| 1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ |
| 2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ |
| 3 | #include "countryflags.h" |
| 4 | |
| 5 | #include <base/dbg.h> |
| 6 | #include <base/io.h> |
| 7 | #include <base/log.h> |
| 8 | #include <base/math.h> |
| 9 | #include <base/str.h> |
| 10 | |
| 11 | #include <engine/graphics.h> |
| 12 | #include <engine/shared/config.h> |
| 13 | #include <engine/shared/linereader.h> |
| 14 | #include <engine/storage.h> |
| 15 | |
| 16 | bool CCountryFlags::CCountryFlag::operator<(const CCountryFlag &Other) const |
| 17 | { |
| 18 | return str_comp(a: m_aCountryCodeString, b: Other.m_aCountryCodeString) < 0; |
| 19 | } |
| 20 | |
| 21 | bool CCountryFlags::ValidateCountryCodeString(const char *pString) |
| 22 | { |
| 23 | const size_t Length = str_length(str: pString); |
| 24 | if(Length >= sizeof(CCountryFlag{}.m_aCountryCodeString)) |
| 25 | { |
| 26 | log_error("countryflags" , "Country name '%s' is too long (max. %" PRIzu " characters)" , pString, sizeof(CCountryFlag{}.m_aCountryCodeString) - 1); |
| 27 | return false; |
| 28 | } |
| 29 | |
| 30 | for(size_t Index = 0; Index < Length; ++Index) |
| 31 | { |
| 32 | if(pString[Index] != '-' && |
| 33 | !(pString[Index] >= 'a' && pString[Index] <= 'z') && |
| 34 | !(pString[Index] >= 'A' && pString[Index] <= 'Z')) |
| 35 | { |
| 36 | log_error("countryflags" , "Country name '%s' must only contain letters and dash characters" , pString); |
| 37 | return false; |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | return true; |
| 42 | } |
| 43 | |
| 44 | void CCountryFlags::LoadCountryflagsIndexfile() |
| 45 | { |
| 46 | m_vCountryFlags.clear(); |
| 47 | |
| 48 | const char *pFilename = "countryflags/index.txt" ; |
| 49 | CLineReader LineReader; |
| 50 | if(LineReader.OpenFile(File: Storage()->OpenFile(pFilename, Flags: IOFLAG_READ, Type: IStorage::TYPE_ALL))) |
| 51 | { |
| 52 | while(const char *pLine = LineReader.Get()) |
| 53 | { |
| 54 | if(pLine[0] == '\0' || pLine[0] == '#') // Skip empty lines and comments |
| 55 | { |
| 56 | continue; |
| 57 | } |
| 58 | |
| 59 | if(!ValidateCountryCodeString(pString: pLine)) |
| 60 | { |
| 61 | continue; |
| 62 | } |
| 63 | CCountryFlag CountryFlag; |
| 64 | str_copy(dst&: CountryFlag.m_aCountryCodeString, src: pLine); |
| 65 | |
| 66 | const char *pCountryCodeLine = LineReader.Get(); |
| 67 | if(!pCountryCodeLine) |
| 68 | { |
| 69 | log_error("countryflags" , "Unexpected end of index file after country '%s'" , CountryFlag.m_aCountryCodeString); |
| 70 | break; |
| 71 | } |
| 72 | |
| 73 | if(!str_startswith(str: pCountryCodeLine, prefix: "== " )) |
| 74 | { |
| 75 | log_error("countryflags" , "Malformed country code for country '%s'" , CountryFlag.m_aCountryCodeString); |
| 76 | continue; |
| 77 | } |
| 78 | pCountryCodeLine += str_length(str: "== " ); |
| 79 | |
| 80 | if(!str_toint(str: pCountryCodeLine, out: &CountryFlag.m_CountryCode)) |
| 81 | { |
| 82 | log_error("countryflags" , "Country code '%s' for country '%s' is not a number" , pCountryCodeLine, CountryFlag.m_aCountryCodeString); |
| 83 | continue; |
| 84 | } |
| 85 | if(!in_range(a: CountryFlag.m_CountryCode, lower: CountryCode::MINIMUM, upper: CountryCode::MAXIMUM)) |
| 86 | { |
| 87 | log_error("countryflags" , "Country code '%d' for country '%s' is not within valid code range [%d..%d]" , CountryFlag.m_CountryCode, CountryFlag.m_aCountryCodeString, CountryCode::MINIMUM, CountryCode::MAXIMUM); |
| 88 | continue; |
| 89 | } |
| 90 | if(CountryFlag.m_CountryCode == CountryCode::DEFAULT && str_comp(a: CountryFlag.m_aCountryCodeString, b: "default" ) != 0) |
| 91 | { |
| 92 | log_error("countryflags" , "Country code '%d' for country '%s' is only allowed for the default country" , CountryFlag.m_CountryCode, CountryFlag.m_aCountryCodeString); |
| 93 | continue; |
| 94 | } |
| 95 | |
| 96 | char aFlagPath[IO_MAX_PATH_LENGTH]; |
| 97 | CImageInfo ImageInfo; |
| 98 | str_format(buffer: aFlagPath, buffer_size: sizeof(aFlagPath), format: "countryflags/%s.png" , CountryFlag.m_aCountryCodeString); |
| 99 | if(!Graphics()->LoadPng(Image&: ImageInfo, pFilename: aFlagPath, StorageType: IStorage::TYPE_ALL)) |
| 100 | { |
| 101 | log_error("countryflags" , "Failed to load country flag from '%s'" , aFlagPath); |
| 102 | continue; |
| 103 | } |
| 104 | |
| 105 | CountryFlag.m_Texture = Graphics()->LoadTextureRawMove(Image&: ImageInfo, Flags: 0, pTexName: aFlagPath); |
| 106 | if(g_Config.m_Debug) |
| 107 | { |
| 108 | log_trace("countryflags" , "Loaded country flag '%s'" , CountryFlag.m_aCountryCodeString); |
| 109 | } |
| 110 | m_vCountryFlags.push_back(x: CountryFlag); |
| 111 | } |
| 112 | } |
| 113 | else |
| 114 | { |
| 115 | log_error("countryflags" , "Failed to open country flags index file '%s'" , pFilename); |
| 116 | } |
| 117 | |
| 118 | // Ensure a default flag exists |
| 119 | auto ExistingDefaultFlag = std::find_if(first: m_vCountryFlags.begin(), last: m_vCountryFlags.end(), pred: [](const CCountryFlag &Flag) { |
| 120 | return Flag.m_CountryCode == CountryCode::DEFAULT; |
| 121 | }); |
| 122 | if(ExistingDefaultFlag == m_vCountryFlags.end()) |
| 123 | { |
| 124 | CCountryFlag DefaultFlag; |
| 125 | DefaultFlag.m_CountryCode = CountryCode::DEFAULT; |
| 126 | str_copy(dst&: DefaultFlag.m_aCountryCodeString, src: "default" ); |
| 127 | m_vCountryFlags.push_back(x: DefaultFlag); |
| 128 | } |
| 129 | |
| 130 | std::sort(first: m_vCountryFlags.begin(), last: m_vCountryFlags.end()); |
| 131 | |
| 132 | size_t DefaultIndex = 0; |
| 133 | for(size_t Index = 0; Index < m_vCountryFlags.size(); ++Index) |
| 134 | { |
| 135 | if(m_vCountryFlags[Index].m_CountryCode == CountryCode::DEFAULT) |
| 136 | { |
| 137 | DefaultIndex = Index; |
| 138 | break; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | std::fill(first: std::begin(arr&: m_aCountryCodeToIndexTable), last: std::end(arr&: m_aCountryCodeToIndexTable), value: DefaultIndex); |
| 143 | for(size_t i = 0; i < m_vCountryFlags.size(); ++i) |
| 144 | { |
| 145 | m_aCountryCodeToIndexTable[m_vCountryFlags[i].m_CountryCode - CountryCode::MINIMUM] = i; |
| 146 | } |
| 147 | |
| 148 | log_debug("countryflags" , "Loaded %" PRIzu " country flags" , m_vCountryFlags.size()); |
| 149 | } |
| 150 | |
| 151 | void CCountryFlags::OnInit() |
| 152 | { |
| 153 | LoadCountryflagsIndexfile(); |
| 154 | |
| 155 | m_FlagsQuadContainerIndex = Graphics()->CreateQuadContainer(AutomaticUpload: false); |
| 156 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
| 157 | Graphics()->QuadsSetSubset(TopLeftU: 0.0f, TopLeftV: 0.0f, BottomRightU: 1.0f, BottomRightV: 1.0f); |
| 158 | Graphics()->QuadContainerAddSprite(QuadContainerIndex: m_FlagsQuadContainerIndex, X: 0.0f, Y: 0.0f, Width: 1.0f, Height: 1.0f); |
| 159 | Graphics()->QuadContainerUpload(ContainerIndex: m_FlagsQuadContainerIndex); |
| 160 | } |
| 161 | |
| 162 | size_t CCountryFlags::Num() const |
| 163 | { |
| 164 | return m_vCountryFlags.size(); |
| 165 | } |
| 166 | |
| 167 | const CCountryFlags::CCountryFlag &CCountryFlags::GetByCountryCode(int CountryCode) const |
| 168 | { |
| 169 | dbg_assert(CountryCode >= CountryCode::MINIMUM && CountryCode <= CountryCode::MAXIMUM, "Invalid CountryCode: %d" , CountryCode); |
| 170 | return GetByIndex(Index: m_aCountryCodeToIndexTable[CountryCode - CountryCode::MINIMUM]); |
| 171 | } |
| 172 | |
| 173 | const CCountryFlags::CCountryFlag &CCountryFlags::GetByIndex(size_t Index) const |
| 174 | { |
| 175 | dbg_assert(Index < m_vCountryFlags.size(), "Invalid Index: %" PRIzu, Index); |
| 176 | return m_vCountryFlags[Index]; |
| 177 | } |
| 178 | |
| 179 | void CCountryFlags::Render(const CCountryFlag &Flag, ColorRGBA Color, float x, float y, float w, float h) |
| 180 | { |
| 181 | if(Flag.m_Texture.IsValid()) |
| 182 | { |
| 183 | Graphics()->TextureSet(Texture: Flag.m_Texture); |
| 184 | Graphics()->SetColor(Color); |
| 185 | Graphics()->QuadsSetRotation(Angle: 0.0f); |
| 186 | Graphics()->RenderQuadContainerEx(ContainerIndex: m_FlagsQuadContainerIndex, QuadOffset: 0, QuadDrawNum: -1, X: x, Y: y, ScaleX: w, ScaleY: h); |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | void CCountryFlags::Render(int CountryCode, ColorRGBA Color, float x, float y, float w, float h) |
| 191 | { |
| 192 | Render(Flag: GetByCountryCode(CountryCode), Color, x, y, w, h); |
| 193 | } |
| 194 | |