/* FinalSun/FinalAlert 2 Mission Editor Copyright (C) 1999-2024 Electronic Arts, Inc. Authored by Matthias Wagner This program 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // standard functions #include "stdafx.h" #include "FinalSunDlg.h" #include "TSoptions.h" #include "variables.h" #include "functions.h" #include "inlines.h" #include "mmsystem.h" #include "IniMega.h" #include #define DBG #undef DBG #define DBG2 #undef DBG2 bool isValidUtf8(const char* utf8) { // wstring_convert and codecvt_utf8_utf16 are deprecated in C++17, fallback to Win32 auto utf8Count = strlen(utf8); if (utf8Count == 0) return true; // unterminatedCountWChars will be the count of WChars NOT including the terminating zero (due to passing in utf8.size() instead of -1) auto unterminatedCountWChars = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8, utf8Count, nullptr, 0); return unterminatedCountWChars > 0; } std::wstring utf8ToUtf16(const std::string& utf8) { // wstring_convert and codecvt_utf8_utf16 are deprecated in C++17, fallback to Win32 if (utf8.size() == 0) // MultiByteToWideChar does not support passing in cbMultiByte == 0 return L""; // unterminatedCountWChars will be the count of WChars NOT including the terminating zero (due to passing in utf8.size() instead of -1) auto utf8Count = utf8.size(); auto unterminatedCountWChars = MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED | MB_ERR_INVALID_CHARS, utf8.data(), utf8Count, nullptr, 0); if (unterminatedCountWChars == 0) { throw std::runtime_error("UTF8 -> UTF16 conversion failed"); } std::wstring utf16; utf16.resize(unterminatedCountWChars); if (MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED | MB_ERR_INVALID_CHARS, utf8.data(), utf8Count, utf16.data(), unterminatedCountWChars) == 0) { throw std::runtime_error("UTF8 -> UTF16 conversion failed"); } return utf16; } std::wstring utf8ToUtf16(const char* utf8) { // wstring_convert and codecvt_utf8_utf16 are deprecated in C++17, fallback to Win32 auto utf8Count = strlen(utf8); if (utf8Count == 0) // MultiByteToWideChar does not support passing in cbMultiByte == 0 return L""; // unterminatedCountWChars will be the count of WChars NOT including the terminating zero (due to passing in utf8.size() instead of -1) auto unterminatedCountWChars = MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED | MB_ERR_INVALID_CHARS, utf8, utf8Count, nullptr, 0); if (unterminatedCountWChars == 0) { throw std::runtime_error("UTF8 -> UTF16 conversion failed"); } std::wstring utf16; utf16.resize(unterminatedCountWChars); if (MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED | MB_ERR_INVALID_CHARS, utf8, utf8Count, utf16.data(), unterminatedCountWChars) == 0) { throw std::runtime_error("UTF8 -> UTF16 conversion failed"); } return utf16; } std::string utf16ToCP(const std::wstring& utf16, int CP) { // wstring_convert and codecvt_utf8_utf16 are deprecated in C++17, fallback to Win32 if (utf16.size() == 0) // WideCharToMultiByte does not support passing in cbMultiByte == 0 return ""; // unterminatedCountWChars will be the count of WChars NOT including the terminating zero (due to passing in utf8.size() instead of -1) auto utf16Count = utf16.size(); auto unterminatedCountChars = WideCharToMultiByte(CP, CP == CP_UTF8 ? WC_ERR_INVALID_CHARS : 0, utf16.data(), utf16Count, nullptr, 0, nullptr, nullptr); if (unterminatedCountChars == 0) { throw std::runtime_error(CP == CP_UTF8 ? "UTF16 -> UTF8 conversion failed" : "UTF16 -> MultiByte conversion failed"); } std::string cps; cps.resize(unterminatedCountChars); if (WideCharToMultiByte(CP, CP == CP_UTF8 ? WC_ERR_INVALID_CHARS : 0, utf16.data(), utf16Count, cps.data(), unterminatedCountChars, nullptr, nullptr) == 0) { throw std::runtime_error(CP == CP_UTF8 ? "UTF16 -> UTF8 conversion failed" : "UTF16 -> MultiByte conversion failed"); } return cps; } std::string utf16ToUtf8(const std::wstring& utf16) { return utf16ToCP(utf16, CP_UTF8); } std::string utf16ToACP(const std::wstring& utf16) { return utf16ToCP(utf16, CP_ACP); } // strcpy for overlapping strings char* strcpy_safe(char* strDestination, const char* strSource) { /*char* buffer=new(char[strlen(strSource)+1]); strcpy(buffer, strSource); strcpy(strDestination, buffer);*/ int len = strlen(strSource) + 1; memmove(strDestination, strSource, len); return strDestination; } CString TranslateHouse(CString original, BOOL bToUI) { #ifdef RA2_MODE if (bToUI) { // CCStrings[*rules.sections[HOUSES].GetValue(i)].wString for (auto const& pair : rules.GetSection(HOUSES)) { original.Replace(pair.second, CCStrings[pair.second].cString); } } else { for (auto const& pair : rules.GetSection(HOUSES)) { original.Replace(CCStrings[pair.second].cString, pair.second); } } #endif return original; } bool deleteFile(const std::string& u8FilePath) { return DeleteFileW(utf8ToUtf16(u8FilePath).c_str()) ? true : false; } // set the status bar text in the main dialog void SetMainStatusBar(const char* text) { CFinalSunDlg* dlg = (CFinalSunDlg*)theApp.GetMainWnd(); dlg->SetText(text); } // set the status bar text in the main dialog to ready void SetMainStatusBarReady() { CFinalSunDlg* dlg = (CFinalSunDlg*)theApp.GetMainWnd(); dlg->SetReady();; } // Should not be required anymore bool RepairTrigger(CString& triggerdata) { if (GetParam(triggerdata, 3).IsEmpty()) { triggerdata = SetParam(triggerdata, 3, "0"); return true; } if (GetParam(triggerdata, 4).IsEmpty()) { triggerdata = SetParam(triggerdata, 4, "1"); return true; } if (GetParam(triggerdata, 5).IsEmpty()) { triggerdata = SetParam(triggerdata, 5, "1"); return true; } if (GetParam(triggerdata, 6).IsEmpty()) { triggerdata = SetParam(triggerdata, 6, "1"); return true; } if (GetParam(triggerdata, 7).IsEmpty()) { triggerdata = SetParam(triggerdata, 7, "0"); return true; } return false; } // make some UI noise void Sound(int ID) { if (theApp.m_Options.bNoSounds) return; if (ID == SOUND_NONE) return; LPCSTR lpSound = NULL; if (ID == SOUND_POSITIVE) { lpSound = MAKEINTRESOURCE(IDR_WAVE1); } else if (ID == SOUND_NEGATIVE) lpSound = MAKEINTRESOURCE(IDR_WAVE2); else if (ID == SOUND_LAYDOWNTILE) lpSound = MAKEINTRESOURCE(IDR_WAVE3); if (lpSound) { PlaySound(lpSound, GetModuleHandle(NULL), SND_ASYNC | SND_RESOURCE); } } void HandleParamList(CComboBox& cb, int type) { CString oldText; cb.GetWindowText(oldText); switch (type) { case PARAMTYPE_NOTHING: { while (cb.DeleteString(0) != CB_ERR); cb.SetWindowText(oldText); //cb.AddString("0"); } break; case PARAMTYPE_HOUSES: ListHouses(cb, TRUE, TRUE, TRUE); break; case PARAMTYPE_WAYPOINTS: ListWaypoints(cb); break; case PARAMTYPE_TEAMTYPES: ListTeamTypes(cb, FALSE); break; case PARAMTYPE_UNITTYPES: ListUnits(cb); break; case PARAMTYPE_INFANTRYTYPES: ListInfantry(cb); break; case PARAMTYPE_AIRCRAFTTYPES: ListAircraft(cb); break; case PARAMTYPE_BUILDINGTYPES: ListBuildings(cb); break; case PARAMTYPE_VIDEOS: ListMovies(cb, FALSE, TRUE); break; case PARAMTYPE_TUTORIALTEXTS: ListTutorial(cb); break; case PARAMTYPE_TRIGGERS: ListTriggers(cb); break; case PARAMTYPE_YESNO: ListYesNo(cb); break; case PARAMTYPE_SOUNDS: ListSounds(cb); break; case PARAMTYPE_THEMES: ListThemes(cb); break; case PARAMTYPE_SPEECHES: ListSpeeches(cb); break; case PARAMTYPE_SPECIALWEAPONS: ListSpecialWeapons(cb); break; case PARAMTYPE_ANIMATIONS: ListAnimations(cb); break; case PARAMTYPE_PARTICLES: ListParticles(cb); break; case PARAMTYPE_CRATETYPES: ListCrateTypes(cb); break; case PARAMTYPE_SPEECHBUBBLETYPES: ListSpeechBubbleTypes(cb); break; case PARAMTYPE_TAGS: ListTags(cb, false); break; case PARAMTYPE_GLOBALS: ListMapVariables(cb); break; case PARAMTYPE_RULESGLOBALS: ListRulesGlobals(cb); break; case PARAMTYPE_BUILDINGTYPESINI: ListBuildings(cb, true); break; case PARAMTYPE_TECHTYPES: ListTechtypes(cb); break; } } void ShowOptionsDialog(CIniFile& optIni) { // show the options dialog, and save the options. #ifdef RA2_MODE CString game = "RA2"; CString app = "FinalAlert"; #else CString game = "TS"; CString app = "FinalSun"; #endif std::string iniFile = ""; iniFile = u8AppDataPath; #ifndef RA2_MODE iniFile += "\\FinalSun.ini"; #else iniFile += "\\FinalAlert.ini"; #endif optIni.LoadFile(iniFile); CTSOptions opt; opt.m_TSEXE = theApp.m_Options.TSExe; if (opt.DoModal() == IDCANCEL) { return; } theApp.m_Options.TSExe = opt.m_TSEXE; optIni.SetString(game, "Exe", theApp.m_Options.TSExe); optIni.SetString(app, "Language", opt.m_LanguageName); BOOL bOldSearch = theApp.m_Options.bSearchLikeTS; if (!(opt.m_LikeTS == 1)) { optIni.SetString(app, "FileSearchLikeGame", "yes"); theApp.m_Options.bSearchLikeTS = TRUE; } else { theApp.m_Options.bSearchLikeTS = FALSE; optIni.SetString(app, "FileSearchLikeGame", "no"); } auto bOldPreferLocalTheaterFiles = theApp.m_Options.bPreferLocalTheaterFiles; theApp.m_Options.bPreferLocalTheaterFiles = opt.m_PreferLocalTheaterFiles ? true : false; optIni.SetString(app, "PreferLocalTheaterFiles", theApp.m_Options.bPreferLocalTheaterFiles ? "1" : "0"); if ( ( (bOldPreferLocalTheaterFiles != theApp.m_Options.bPreferLocalTheaterFiles) || (bOldSearch != theApp.m_Options.bSearchLikeTS) ) && bOptionsStartup == FALSE) { MessageBox(0, GetLanguageStringACP("RestartNeeded"), "Restart", 0); } CString oldLang = theApp.m_Options.LanguageName; theApp.m_Options.LanguageName = opt.m_LanguageName; if (oldLang != theApp.m_Options.LanguageName && theApp.m_pMainWnd != NULL && theApp.m_pMainWnd->m_hWnd != NULL) { ((CFinalSunDlg*)theApp.m_pMainWnd)->UpdateStrings(); } optIni.SaveFile(iniFile); } BOOL DoesFileExist(LPCSTR szFile) { std::wstring file = utf8ToUtf16(szFile); HANDLE hFound = CreateFileW(file.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFound != INVALID_HANDLE_VALUE) { CloseHandle(hFound); return TRUE; } return FALSE; } CString ToACP(const CString& utf8) { // convert to process codepage (should be UTF8 on newer systems) auto cp = GetACP(); return (cp == CP_UTF8) ? utf8 : CString(utf16ToACP(utf8ToUtf16(utf8)).c_str()); } // change all %n (where n is an int) in an string, to another specified string CString TranslateStringVariables(int n, const char* originaltext, const char* inserttext) { char c[50]; _itoa_s(n, c, 10); char seekedstring[50]; seekedstring[0] = '%'; seekedstring[1] = 0; strcat_s(seekedstring, c); CString orig = originaltext; if (orig.Find(seekedstring) < 0) { return orig; } orig.Replace(seekedstring, inserttext); return orig; } const CString* getLanguageString(const CString& key) { #ifdef RA2_MODE auto const strRA2Sec = theApp.m_Options.LanguageName + "-StringsRA2"; if (auto const& translated = language.GetSection(strRA2Sec).TryGetString(key)) { return translated; } // fallback to english if (auto const& def = language.GetSection("English-StringsRA2").TryGetString(key)) { return def; } #endif // last try auto const defSec = theApp.m_Options.LanguageName + "-Strings"; if (auto const& def = language.GetSection(defSec).TryGetString(key)) { return def; } // last try return language.GetSection("English-Strings").TryGetString(key); } CString EscapeString(const CString& input) { auto updated = input; if (updated.Find("\\n")) { updated.Replace("\\n", "\n"); } return updated; } // retrieve the string name in the correct language (name is an ID). CString GetLanguageStringACP(const CString name) { #ifndef RA2_MODE auto const pStrToInsert = "FinalSun"; #elif YR_MODE auto const pStrToInsert = "FinalAlert 2: Yuri's Revenge"; #else auto const pStrToInsert = "FinalAlert 2"; #endif auto translated = getLanguageString(name); if (!translated) { CString encoded = name; encoded = TranslateStringVariables(9, encoded, pStrToInsert); return ToACP(encoded); } CString encoded; encoded = *translated; if (encoded.Find("%9") >= 0) { encoded = TranslateStringVariables(9, encoded, pStrToInsert); } return ToACP(encoded); } CString TranslateStringACP(WCHAR* u16EnglishString) { return TranslateStringACP(utf16ToUtf8(u16EnglishString).c_str()); } // tranlate a string/word by using the table from english to the current language CString TranslateStringACP(CString u8EnglishString) { if (!isValidUtf8(u8EnglishString)) { errstream << "TranslateStringACP(\"" << u8EnglishString << "\") called with an invalid UTF-8 string" << std::endl; return u8EnglishString; } return GetLanguageStringACP(u8EnglishString); } void TranslateDlgItem(CWnd& cwnd, int controlID, const CString& label) { auto const translated = getLanguageString(label); if (translated) { cwnd.SetDlgItemText(controlID, EscapeString(*translated)); } } void TranslateWindowCaption(CWnd& cwnd, const CString& label) { auto const translated = getLanguageString(label); if (translated) { cwnd.SetWindowText(EscapeString(*translated)); } } void TruncSpace(string& str) { CString cstr = str.data(); TruncSpace(cstr); str = cstr; } void TruncSpace(CString& str) { str.TrimLeft(); str.TrimRight(); auto const spacePos = str.Find(" "); if (spacePos >= 0) { str.Delete(spacePos, str.GetLength() - spacePos); } } CString GetText(CWnd* wnd) { CString str; wnd->GetWindowText(str); return str; } CString GetText(CSliderCtrl* wnd) { int v = wnd->GetPos(); char c[150]; _itoa_s(v, c, 10); return(c); } CString GetText(CComboBox* wnd) { CString str; if (wnd->GetCurSel() != -1) { wnd->GetLBText(wnd->GetCurSel(), str); return str; } wnd->GetWindowText(str); return(str); } void Info(const char* data, string& house, string& type, int& strength, int& x, int& y, string& other) { other = ""; house = GetParam(data, 0); type = GetParam(data, 1); strength = atoi(GetParam(data, 2)); y = atoi(GetParam(data, 3)); x = atoi(GetParam(data, 4)); CString tmp; BOOL takeABreak = FALSE; int i = 1; do { tmp = GetParam(data, 4 + i); //MessageBox(0,tmp.data(),"",0); if (tmp != "") { other += ","; other += tmp; } else { takeABreak = TRUE; break; } i++; } while (takeABreak == FALSE); }; void UnitInfo(const char* data, string& house, string& type, int& strength, int& x, int& y, int& direction, string& action, string& actiontrigger, int& u1, int& u2, int& u3, int& u4, int& u5, int& u6) { house = GetParam(data, 0); type = GetParam(data, 1); strength = atoi(GetParam(data, 2)); y = atoi(GetParam(data, 3)); x = atoi(GetParam(data, 4)); direction = atoi(GetParam(data, 5)); action = GetParam(data, 6); actiontrigger = GetParam(data, 7); u1 = atoi(GetParam(data, 8)); u2 = atoi(GetParam(data, 9)); u3 = atoi(GetParam(data, 10)); u4 = atoi(GetParam(data, 11)); u5 = atoi(GetParam(data, 12)); u6 = atoi(GetParam(data, 13)); } void AirInfo(const char* data, string& house, string& type, int& strength, int& x, int& y, int& direction, string& action, string& actiontrigger, int& u1, int& u2, int& u3, int& u4) { house = GetParam(data, 0); type = GetParam(data, 1); strength = atoi(GetParam(data, 2)); y = atoi(GetParam(data, 3)); x = atoi(GetParam(data, 4)); direction = atoi(GetParam(data, 5)); action = GetParam(data, 6); actiontrigger = GetParam(data, 7); u1 = atoi(GetParam(data, 8)); u2 = atoi(GetParam(data, 9)); u3 = atoi(GetParam(data, 10)); u4 = atoi(GetParam(data, 11)); } void InfanteryInfo(const char* data, string& house, string& type, int& strength, int& x, int& y, int& pos, string& action, int& direction, string& actiontrigger, int& u1, int& u2, int& u3, int& u4, int& u5) { house = GetParam(data, 0); type = GetParam(data, 1); strength = atoi(GetParam(data, 2)); y = atoi(GetParam(data, 3)); x = atoi(GetParam(data, 4)); pos = atoi(GetParam(data, 5)); action = GetParam(data, 6); direction = atoi(GetParam(data, 7)); actiontrigger = GetParam(data, 8); u1 = atoi(GetParam(data, 9)); u2 = atoi(GetParam(data, 10)); u3 = atoi(GetParam(data, 11)); u4 = atoi(GetParam(data, 12)); u5 = atoi(GetParam(data, 12)); } void StructureInfo(const char* data, string& house, string& type, int& strength, int& x, int& y, int& direction, string& action, int& u1, int& u2, int& energy, int& upgrades, int& u5, string& upgrade1, string& upgrade2, string& upgrade3, int& u9, int& u10) { house = GetParam(data, 0); type = GetParam(data, 1); strength = atoi(GetParam(data, 2)); y = atoi(GetParam(data, 3)); x = atoi(GetParam(data, 4)); direction = atoi(GetParam(data, 5)); action = GetParam(data, 6); u1 = atoi(GetParam(data, 7)); u2 = atoi(GetParam(data, 8)); energy = atoi(GetParam(data, 9)); upgrades = atoi(GetParam(data, 10)); u5 = atoi(GetParam(data, 11)); upgrade1 = GetParam(data, 12); upgrade2 = GetParam(data, 13); upgrade3 = GetParam(data, 14); u9 = atoi(GetParam(data, 15)); u10 = atoi(GetParam(data, 16)); } void listSpecifcTechnoTypes(CComboBox& cb, const CString& sectionName, bool clear = true, bool useIniName = false) { if (clear) { while (cb.DeleteString(0) != CB_ERR); } auto const& sec = rules.GetSection(sectionName); for (auto idx = 0; idx < sec.Size(); ++idx) { char idxNum[50]; _itoa_s(idx, idxNum, 10); CString record = idxNum; auto const& kvPair = sec.Nth(idx); if (useIniName) { record = kvPair.second; } else { record += ' '; record += kvPair.second; } record += " "; CString translated = Map->GetUnitName(kvPair.second); //if(t!="MISSING") { record += translated; cb.AddString(record); } } } void listSpecifcTypesWithSequence(CComboBox& cb, const CString& sectionName, bool clear = true) { if (clear) { while (cb.DeleteString(0) != CB_ERR); } auto const& sec = rules.GetSection(sectionName); for (auto idx = 0; idx < sec.Size(); ++idx) { char idxNum[50]; _itoa_s(idx, idxNum, 10); auto const& kvPair = sec.Nth(idx); CString record = idxNum; record += " "; record += kvPair.second; cb.AddString(record); } } void ListBuildings(CComboBox& cb, bool bININame) { listSpecifcTechnoTypes(cb, "BuildingTypes", true, bININame); } void ListInfantry(CComboBox& cb) { listSpecifcTechnoTypes(cb, "InfantryTypes"); } void ListUnits(CComboBox& cb) { listSpecifcTechnoTypes(cb, "VehicleTypes"); } void ListAircraft(CComboBox& cb) { listSpecifcTechnoTypes(cb, "AircraftTypes"); } void ListTechtypes(CComboBox& cb) { while (cb.DeleteString(0) != CB_ERR); listSpecifcTechnoTypes(cb, "AircraftTypes", false, true); listSpecifcTechnoTypes(cb, "InfantryTypes", false, true); listSpecifcTechnoTypes(cb, "VehicleTypes", false, true); listSpecifcTechnoTypes(cb, "BuildingTypes", false, true); } void listLocalVariables(CComboBox& cb, const CIniFile& ini) { while (cb.DeleteString(0) != CB_ERR); for (auto const& kvPair : ini.GetSection("VariableNames")) { auto const desc = kvPair.first + " " + kvPair.second; cb.AddString(desc); } } // should be ListLocals() void ListMapVariables(CComboBox& cb) { listLocalVariables(cb, Map->GetIniFile()); } void ListRulesGlobals(CComboBox& cb) { listLocalVariables(cb, rules); } extern TranslationMap AllStrings; void ListTutorial(CComboBox& cb) { while (cb.DeleteString(0) != CB_ERR); #ifndef RA2_MODE int i; for (i = 0; i < tutorial.sections["Tutorial"].values.size(); i++) { CString s; s = *tutorial.sections["Tutorial"].GetValueName(i); s += " "; s += *tutorial.sections["Tutorial"].GetValue(i); cb.AddString(s); } #else typedef map::iterator it; it _it = AllStrings.begin(); /*it begin; it end; begin=CCStrings.begin(); end=CCStrings.end();*/ int i; for (i = 0; i < CCStrings.size(); i++) { CString s; s = _it->first; s += " : "; s += _it->second.cString; cb.AddString(s); _it++; } #endif } void ListTriggers(CComboBox& cb) { while (cb.DeleteString(0) != CB_ERR); CIniFile& ini = Map->GetIniFile(); for (auto const& kvPair : ini.GetSection("Triggers")) { auto s = kvPair.first; s += " ("; s += GetParam(kvPair.second, 2); s += ")"; cb.AddString(s); } } void ListYesNo(CComboBox& cb) { while (cb.DeleteString(0) != CB_ERR); cb.AddString("1 " + GetLanguageStringACP("Yes")); cb.AddString("0 " + GetLanguageStringACP("No")); } void ListSounds(CComboBox& cb) { while (cb.DeleteString(0) != CB_ERR); #ifdef RA2_MODE for (auto const& kvPair : sound.GetSection("SoundList")) { cb.AddString(kvPair.second); } #endif } void ListThemes(CComboBox& cb) { while (cb.DeleteString(0) != CB_ERR); #ifdef RA2_MODE for (auto const& kvPair : theme.GetSection("Themes")) { CString record = kvPair.second;// now is ini ID TruncSpace(record); if (record.IsEmpty()) { continue; } record += " "; record += AllStrings[sound.GetSection(record).GetString("Name")].cString; cb.AddString(record); } #endif } void ListSpeeches(CComboBox& cb) { while (cb.DeleteString(0) != CB_ERR); #ifdef RA2_MODE for (auto const& kvPair : eva.GetSection("DialogList")) { cb.AddString(kvPair.second); } #endif } void ListSpecialWeapons(CComboBox& cb) { listSpecifcTypesWithSequence(cb, "SuperWeaponTypes"); } void ListAnimations(CComboBox& cb) { listSpecifcTypesWithSequence(cb, "Animations"); } void ListParticles(CComboBox& cb) { listSpecifcTypesWithSequence(cb, "Particles"); } void ListCrateTypes(CComboBox& cb) { while (cb.DeleteString(0) != CB_ERR); } void ListSpeechBubbleTypes(CComboBox& cb) { while (cb.DeleteString(0) != CB_ERR); } void ListMovies(CComboBox& cb, BOOL bListNone, BOOL bListParam) { if (!bListParam) { int sel = cb.GetCurSel(); while (cb.DeleteString(0) != CB_ERR); if (bListNone) { cb.AddString(""); } auto const& movieList = art.GetSection("Movies"); for (auto idx = 0; idx < movieList.Size(); ++idx) { if (idx < atoi(g_data.GetString("MovieList", "Start"))) { continue; } CString movieID = movieList.Nth(idx).second; cb.AddString(movieID); } if (sel >= 0) { cb.SetCurSel(sel); } return; } while (cb.DeleteString(0) != CB_ERR); auto const& movieList = art.GetSection("Movies"); char idxStr[50]; for (auto i = 0; i < movieList.Size(); i++) { if (i < atoi(g_data.GetString("MovieList", "Start"))) { continue; } _itoa_s(i, idxStr, 10); CString desc(idxStr); desc += " "; desc += movieList.Nth(i).second; cb.AddString(desc); } } void ListTags(CComboBox& cb, BOOL bListNone) { CIniFile& ini = Map->GetIniFile(); int sel = cb.GetCurSel(); while (cb.DeleteString(0) != CB_ERR); int i; if (bListNone) { cb.AddString("None"); } for (auto const& kvPair : ini.GetSection("Tags")) { CString s = kvPair.first; s += " "; s += GetParam(kvPair.second, 1); cb.AddString(s); } if (sel >= 0) { cb.SetCurSel(sel); } } int GetRulesHousesSize() { return rules.GetSection(HOUSES).Size(); } // a bug adds an empty house to the rules section, delete it here int RepairRulesHouses() { auto const& sec = rules.GetSection(HOUSES); auto toDelete = std::vector(); toDelete.reserve(sec.Size()); for (auto idx = 0; idx < sec.Size(); idx++) { if (sec.Nth(idx).second.IsEmpty()) { toDelete.push_back(idx); } } // ascending sequence std::stable_sort(toDelete.begin(), toDelete.end()); if (!toDelete.empty()) { auto const mutSec = rules.TryGetSection(HOUSES); ASSERT(mutSec != nullptr); for (auto const idx : toDelete) { mutSec->RemoveAt(idx); } } return GetRulesHousesSize(); } // MW 07/27/01: Modified for etc in YR void ListHouses(CComboBox& cb, BOOL bNumbers, BOOL bCountries, BOOL bPlayers) { CIniFile& ini = Map->GetIniFile(); int sel = cb.GetCurSel(); int crulesh = GetRulesHousesSize(); // TODO: align with RN edition if (Map->IsMultiplayer() == FALSE) { bPlayers = FALSE; // really only for multi maps! } CString sSection = bCountries ? HOUSES : MAPHOUSES; while (cb.DeleteString(0) != CB_ERR); // houses: rules.ini + map definitions! auto const mapHouseList = ini.TryGetSection(sSection); if (mapHouseList) { if (mapHouseList->Size() == 0) { goto wasnohouse; } // we use the map definitions! if (yuri_mode && bPlayers) { if (bNumbers) { cb.AddString("4475 "); cb.AddString("4476 "); cb.AddString("4477 "); cb.AddString("4478 "); cb.AddString("4479 "); cb.AddString("4480 "); cb.AddString("4481 "); cb.AddString("4482 "); } else { cb.AddString(""); cb.AddString(""); cb.AddString(""); cb.AddString(""); cb.AddString(""); cb.AddString(""); cb.AddString(""); cb.AddString(""); } } for (auto i = 0; i < mapHouseList->Size(); i++) { CString j; #ifdef RA2_MODE j = mapHouseList->Nth(i).second; j.MakeLower(); if (j == "nod" || j == "gdi") { continue; } #endif if (bNumbers) { char idxStr[50]; _itoa_s(i, idxStr, 10); #ifdef RA2_MODE if (bCountries) { int preexisting = 0; int e; auto const& rulesMapList = rules.GetSection(sSection); for (e = 0; e < i; e++) { if (rulesMapList.HasValue(mapHouseList->Nth(e).second)) { preexisting++; } } if (rulesMapList.HasValue(mapHouseList->Nth(i).second)) { auto const& mapHouseID = mapHouseList->Nth(i).second; auto const& idxInRules = rulesMapList.FindValue(mapHouseID); _itoa_s(idxInRules, idxStr, 10); } else { _itoa_s(i + crulesh - preexisting, idxStr, 10); } } #endif j = idxStr; j += " "; j += TranslateHouse(mapHouseList->Nth(i).second, TRUE); } else { j = TranslateHouse(mapHouseList->Nth(i).second, TRUE); } cb.AddString(j); } } else { wasnohouse: if (bNumbers) { auto const& rulesHouseList = rules.GetSection(HOUSES); for (auto const& [key, val] : rulesHouseList) { CString houseRecord; #ifdef RA2_MODE houseRecord = val; houseRecord.MakeLower(); if (houseRecord == "nod" || houseRecord == "gdi") { continue; } #endif houseRecord = key; houseRecord += " "; houseRecord += TranslateHouse(val, TRUE); cb.AddString(houseRecord); } if (!yuri_mode || !bPlayers) { for (auto i = 0; i < 8; i++) { int k = i; #ifdef RA2_MODE k += crulesh; //rules.sections[HOUSES].values.size(); #endif CString j; char c[50]; _itoa_s(k, c, 10); j += c; j += " Multi-Player "; _itoa_s(i, c, 10); j += c; cb.AddString(j); } } else { cb.AddString("4475 "); cb.AddString("4476 "); cb.AddString("4477 "); cb.AddString("4478 "); cb.AddString("4479 "); cb.AddString("4480 "); cb.AddString("4481 "); cb.AddString("4482 "); } } else { if (yuri_mode && bPlayers) { cb.AddString(""); cb.AddString(""); cb.AddString(""); cb.AddString(""); cb.AddString(""); cb.AddString(""); cb.AddString(""); cb.AddString(""); } auto const& rulesHouseList = rules.GetSection(HOUSES); for (auto const& [key, val] : rulesHouseList) { CString houseRecord; #ifdef RA2_MODE houseRecord = val; houseRecord.MakeLower(); if (houseRecord == "nod" || houseRecord == "gdi") { continue; } #endif if (bNumbers) { houseRecord = key; houseRecord += " "; houseRecord += TranslateHouse(val, TRUE); } else { houseRecord = TranslateHouse(val, TRUE); } cb.AddString(houseRecord); } } } if (sel >= 0) { cb.SetCurSel(sel); } } void ListTeamTypes(CComboBox& cb, BOOL bListNone) { CIniFile& ini = Map->GetIniFile(); int sel = cb.GetCurSel(); while (cb.DeleteString(0) != CB_ERR); int i; if (bListNone) { cb.AddString(""); } auto const& teamTypeList = ini.GetSection("TeamTypes"); for (auto const& [seq, id] : teamTypeList) { auto const& teamTypeDetail = ini.GetSection(id); CString record = id + " " + teamTypeDetail.GetString("Name"); cb.AddString(record); } if (sel >= 0) { cb.SetCurSel(sel); } } void ListWaypoints(CComboBox& cb) { CIniFile& ini = Map->GetIniFile(); int sel = cb.GetCurSel(); while (cb.DeleteString(0) != CB_ERR); for (auto const& [idx, val] : ini.GetSection("Waypoints")) { cb.AddString(idx); } if (sel >= 0) { cb.SetCurSel(sel); } } void ListTargets(CComboBox& cb) { int sel = cb.GetCurSel(); while (cb.DeleteString(0) != CB_ERR); cb.AddString(TranslateStringACP("1 - Not specified")); cb.AddString(TranslateStringACP("2 - Buildings")); cb.AddString(TranslateStringACP("3 - Harvesters")); cb.AddString(TranslateStringACP("4 - Infantry")); cb.AddString(TranslateStringACP("5 - Vehicles")); cb.AddString(TranslateStringACP("6 - Factories")); cb.AddString(TranslateStringACP("7 - Base defenses")); cb.AddString(TranslateStringACP("9 - Power plants")); if (sel >= 0) { cb.SetCurSel(sel); } } void ComboBoxHelper::Clear(CComboBox& combobox) { while (combobox.DeleteString(0) != -1); } void ComboBoxHelper::ListCountries(CComboBox& combobox, bool bShowIndex) { ComboBoxHelper::Clear(combobox); auto& doc = Map->GetIniFile(); bool bMultiOnly = doc.GetBool("Basic", "MultiplayerOnly"); if (bMultiOnly) { ListHouses(combobox, bShowIndex); return; } auto const& rules = IniMegaFile::GetRules(); auto const& items = rules.GetSection("Countries"); CString buffer; for (auto it = items.begin(); it != items.end(); ++it) { auto const& [idxStr, id] = *it; auto const idx = atoi(idxStr); if (bShowIndex) { buffer.Format("%u - %s", idx, id.operator LPCSTR()); } else { buffer = id; } combobox.InsertString(idx, buffer); } } void ComboBoxHelper::ListBoolean(CComboBox& combobox) { ComboBoxHelper::Clear(combobox); combobox.InsertString(0, "0 - FALSE"); combobox.InsertString(1, "1 - TRUE"); } CString GetHouseSectionName(CString lpHouse) { #ifndef RA2_MODE return lpHouse; #else return lpHouse + " House"; #endif } CString GetFreeID() { auto const& ini = Map->GetIniFile(); int n = 1000000; auto isIDInUse = [&ini](const CString& input) { // [TypeList] // 0=TYPE1 // 1=TYPE2 static const CString typeLists[] = { "ScriptTypes", "TaskForces", "TeamTypes", }; // [ItemList] // ID1=SOME_DEFINITION1 // ID2=SOME_DEFINITION2 static const CString itemLists[] = { "Triggers", "Events", "Tags", "Actions", "AITriggerTypes", }; for (auto const& id : typeLists) { for (auto const& [_, id] : ini[id]) { if (id == input) { return true; } } } for (auto const& id : itemLists) { for (auto const& [id, _] : ini[id]) { if (id == input) { return true; } } } return false; }; for (;;) { char p[50]; p[0] = '0'; _itoa_s(n, p + 1, sizeof(p) - 1, 10); if (!isIDInUse(p)) { return p; } n++; } return ""; } void GetNodeName(CString& name, int n) { char c[5]; char p[6]; memset(p, 0, 6); _itoa_s(n, c, 10); strcpy_s(p, c); if (strlen(c) == 1) { memcpy(c, "00", 2); strcpy_s(c + 2, sizeof(c) - 2, p); } else if (strlen(c) == 2) { memcpy(c, "0", 1); strcpy_s(c + 1, sizeof(c) - 1, p); } else if (strlen(c) == 3) { strcpy_s(c, p); } name = c; } int GetNodeAt(CString& owner, CString& buildingTypeID, int x, int y) { CIniFile& ini = Map->GetIniFile(); buildingTypeID = ""; owner = ""; int owners; auto const& houseList = ini.GetSection(HOUSES); if (!houseList.Size()) { return -1; } for (auto const& [idx, ownerID] : houseList) { owner = ownerID; // okay now owner is correct! auto const& ownerSection = ini.GetSection(owner); auto const nodeCount = ownerSection.GetInteger("NodeCount"); for (auto i = 0; i < nodeCount; i++) { CString nodeName; GetNodeName(nodeName, i); CString sx, sy; buildingTypeID = GetParam(ownerSection.GetString(nodeName), 0); sy = GetParam(ownerSection.GetString(nodeName), 1); sx = GetParam(ownerSection.GetString(nodeName), 2); CString arttype = buildingTypeID; auto const& imageID = rules.GetString(buildingTypeID, "Image"); // pointing to another art if (!imageID.IsEmpty()) { arttype = imageID; } // pointing to another art because of map definition auto const overrideImageID = ini.GetString(buildingTypeID, "Image"); if (!overrideImageID.IsEmpty()) { arttype = overrideImageID; } int w, h; char d[6]; memcpy(d, (LPCTSTR)art.GetString(arttype, "Foundation"), 1); d[1] = 0; w = atoi(d); if (w == 0) { w = 1; } memcpy(d, (LPCTSTR)art.GetString(arttype, "Foundation") + 2, 1); d[1] = 0; h = atoi(d); if (h == 0) { h = 1; } int j, k; for (j = 0; j < h; j++) { for (k = 0; k < w; k++) { if (atoi(sx) + j == x && atoi(sy) + k == y) { return i; } } } } } return -1; } std::unique_ptr BitmapFromResource(int resource_id) { std::unique_ptr bm(new CBitmap); if (!bm->LoadBitmap(resource_id)) throw BitmapNotFound(); return bm; } std::unique_ptr BitmapFromFile(const CString& filepath) { std::unique_ptr bm(new CBitmap); if (!bm->LoadBitmap(filepath)) throw BitmapNotFound(); return bm; } /* Returns the area in the current line that should be painted Truncates areas that are transparent, and therefore increases display speed! flags must be set to 0 */ void GetDrawBorder(const BYTE* data, int width, int line, int& left, int& right, unsigned int flags, BOOL* TranspInside) { int i; const BYTE* lpStart = data + line * width; if (flags == 0) { // left border: for (i = 0; i < width; i++) { if (lpStart[i] || i == width - 1) { left = i; break; } } // right border: for (i = width - 1; i >= 0; i--) { if (lpStart[i] || i == 0) { right = i; break; } } if (TranspInside) { for (i = left; i <= right; i++) { if (!lpStart[i]) { *TranspInside = TRUE; break; } } } } } CComPtr BitmapToSurface(IDirectDraw4* pDD, const CBitmap& bitmap) { BITMAP bm; GetObject(bitmap, sizeof(bm), &bm); DDSURFACEDESC2 desc = { 0 }; ZeroMemory(&desc, sizeof(desc)); desc.dwSize = sizeof(desc); desc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; desc.dwWidth = bm.bmWidth; desc.dwHeight = bm.bmHeight; auto pSurface = CComPtr(); if (pDD->CreateSurface(&desc, &pSurface, nullptr) != DD_OK) return nullptr; pSurface->Restore(); CDC bitmapDC; if (!bitmapDC.CreateCompatibleDC(nullptr)) return nullptr; bitmapDC.SelectObject(bitmap); HDC hSurfaceDC = nullptr; if (pSurface->GetDC(&hSurfaceDC) != DD_OK) return nullptr; CDC surfaceDC; surfaceDC.Attach(hSurfaceDC); auto success = surfaceDC.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &bitmapDC, 0, 0, SRCCOPY); surfaceDC.Detach(); pSurface->ReleaseDC(hSurfaceDC); return pSurface; }