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
16bool CCountryFlags::CCountryFlag::operator<(const CCountryFlag &Other) const
17{
18 return str_comp(a: m_aCountryCodeString, b: Other.m_aCountryCodeString) < 0;
19}
20
21bool 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
44void 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
151void 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
162size_t CCountryFlags::Num() const
163{
164 return m_vCountryFlags.size();
165}
166
167const 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
173const 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
179void 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
190void 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