1#ifndef GAME_EDITOR_EDITOR_SERVER_SETTINGS_H
2#define GAME_EDITOR_EDITOR_SERVER_SETTINGS_H
3
4#include "component.h"
5#include "editor_ui.h"
6
7#include <base/str.h>
8
9#include <game/client/lineinput.h>
10#include <game/client/ui_listbox.h>
11
12#include <map>
13#include <string>
14#include <vector>
15
16class CEditor;
17struct SMapSettingInt;
18struct SMapSettingCommand;
19struct IMapSetting;
20class CLineInput;
21
22struct CEditorMapSetting
23{
24 char m_aCommand[256];
25
26 CEditorMapSetting(const char *pCommand)
27 {
28 str_copy(dst&: m_aCommand, src: pCommand);
29 }
30};
31
32// A parsed map setting argument, storing the name and the type
33// Used for validation and to display arguments names
34struct SParsedMapSettingArg
35{
36 char m_aName[32];
37 char m_Type;
38};
39
40// An argument for the current setting
41struct SCurrentSettingArg
42{
43 char m_aValue[256]; // Value of the argument
44 float m_X; // The X position
45 size_t m_Start; // Start offset within the input string
46 size_t m_End; // End offset within the input string
47 bool m_Error; // If the argument is wrong or not
48 char m_ExpectedType; // The expected type
49};
50
51struct SPossibleValueMatch
52{
53 const char *m_pValue; // Possible value string
54 int m_ArgIndex; // Argument for that possible value
55 const void *m_pData; // Generic pointer to pass specific data
56};
57
58struct SCommandParseError
59{
60 char m_aMessage[256];
61 int m_ArgIndex;
62
63 enum EErrorType
64 {
65 ERROR_NONE = 0,
66 ERROR_TOO_MANY_ARGS,
67 ERROR_INVALID_VALUE,
68 ERROR_UNKNOWN_VALUE,
69 ERROR_INCOMPLETE,
70 ERROR_OUT_OF_RANGE,
71 ERROR_UNKNOWN
72 };
73 EErrorType m_Type;
74};
75
76struct SInvalidSetting
77{
78 enum
79 {
80 TYPE_INVALID = 1 << 0,
81 TYPE_DUPLICATE = 1 << 1
82 };
83 int m_Index; // Index of the command in the loaded map settings list
84 char m_aSetting[256]; // String of the setting
85 int m_Type; // Type of that invalid setting
86 int m_CollidingIndex; // The colliding line index in case type is TYPE_DUPLICATE
87 bool m_Unknown; // Is the command unknown
88
89 struct SContext
90 {
91 bool m_Fixed;
92 bool m_Deleted;
93 bool m_Chosen;
94 } m_Context;
95
96 SInvalidSetting(int Index, const char *pSetting, int Type, int CollidingIndex, bool Unknown) :
97 m_Index(Index), m_Type(Type), m_CollidingIndex(CollidingIndex), m_Unknown(Unknown), m_Context()
98 {
99 str_copy(dst&: m_aSetting, src: pSetting);
100 }
101};
102
103// --------------------------------------
104// Builder classes & methods to generate list of possible values
105// easily for specific settings and arguments.
106// It uses a container stored inside CMapSettingsBackend.
107// Usage:
108// CValuesBuilder Builder(&m_Container);
109// // Either do it in one go:
110// Builder("tune").Argument(0).Add("value_1").Add("value_2");
111// // Or reference the builder (useful when using in a loop):
112// auto TuneBuilder = Builder("tune").Argument(0);
113// TuneBuilder.Add("value_1");
114// TuneBuilder.Add("value_2");
115// // ...
116
117using TArgumentValuesList = std::vector<const char *>; // List of possible values
118using TSettingValues = std::map<int, TArgumentValuesList>; // Possible values per argument
119using TSettingsArgumentValues = std::map<std::string, TSettingValues>; // Possible values per argument, per command/setting name
120
121class CValuesBuilder;
122class CSettingValuesBuilder;
123class CArgumentValuesListBuilder
124{
125public:
126 CArgumentValuesListBuilder &Add(const char *pString)
127 {
128 m_pContainer->emplace_back(args&: pString);
129 return *this;
130 }
131
132private:
133 CArgumentValuesListBuilder(std::vector<const char *> *pContainer) :
134 m_pContainer(pContainer) {}
135
136 std::vector<const char *> *m_pContainer;
137 friend class CSettingValuesBuilder;
138};
139
140class CSettingValuesBuilder
141{
142public:
143 CArgumentValuesListBuilder Argument(int Arg) const
144 {
145 return CArgumentValuesListBuilder(&(*m_pContainer)[Arg]);
146 }
147
148private:
149 CSettingValuesBuilder(TSettingValues *pContainer) :
150 m_pContainer(pContainer) {}
151
152 friend class CValuesBuilder;
153 TSettingValues *m_pContainer;
154};
155
156class CValuesBuilder
157{
158public:
159 CValuesBuilder(TSettingsArgumentValues *pContainer) :
160 m_pContainer(pContainer)
161 {
162 }
163
164 CSettingValuesBuilder operator()(const char *pSettingName) const
165 {
166 return CSettingValuesBuilder(&(*m_pContainer)[pSettingName]);
167 }
168
169private:
170 TSettingsArgumentValues *m_pContainer;
171};
172
173// --------------------------------------
174
175struct SValueLoader
176{
177 static void LoadTuneValues(const CSettingValuesBuilder &TuneBuilder);
178 static void LoadTuneZoneValues(const CSettingValuesBuilder &TuneZoneBuilder);
179 static void LoadMapBugs(const CSettingValuesBuilder &BugBuilder);
180 static void LoadArgumentTuneValues(CArgumentValuesListBuilder &&ArgBuilder);
181};
182
183enum class EValidationResult
184{
185 VALID = 0,
186 ERROR,
187 INCOMPLETE,
188 UNKNOWN,
189 OUT_OF_RANGE,
190};
191
192enum class ECollisionCheckResult
193{
194 ERROR,
195 REPLACE,
196 ADD,
197};
198
199class CMapSettingsBackend : public CEditorComponent
200{
201 typedef void (*FLoaderFunction)(const CSettingValuesBuilder &);
202
203public:
204 // General methods
205 CMapSettingsBackend() = default;
206
207 void OnInit(CEditor *pEditor) override;
208 void OnMapLoad() override;
209
210 // Constraints methods
211 enum class EArgConstraint
212 {
213 DEFAULT = 0,
214 UNIQUE,
215 MULTIPLE,
216 };
217
218 EArgConstraint ArgConstraint(const char *pSettingName, int Arg) const
219 {
220 return m_ArgConstraintsPerCommand.at(k: pSettingName).at(k: Arg);
221 }
222
223 // Backend methods
224 const std::vector<SParsedMapSettingArg> &ParsedArgs(const std::shared_ptr<IMapSetting> &pSetting) const
225 {
226 return m_ParsedCommandArgs.at(k: pSetting);
227 }
228
229 // CContext
230 class CContext
231 {
232 static constexpr ColorRGBA ms_ArgumentStringColor = ColorRGBA(84.0f / 255.0f, 1.0f, 1.0f, 1.0f);
233 static constexpr ColorRGBA ms_ArgumentNumberColor = ColorRGBA(0.1f, 0.9f, 0.05f, 1.0f);
234 static constexpr ColorRGBA ms_ArgumentUnknownColor = ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f);
235 static constexpr ColorRGBA ms_CommentColor = ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f);
236 static constexpr ColorRGBA ms_ErrorColor = ColorRGBA(240.0f / 255.0f, 70.0f / 255.0f, 70.0f / 255.0f, 1.0f);
237
238 friend class CMapSettingsBackend;
239
240 public:
241 virtual ~CContext() = default;
242
243 bool CommandIsValid() const { return m_pCurrentSetting != nullptr; }
244 int CurrentArg() const { return m_CursorArgIndex; }
245 const char *CurrentArgName() const { return (!m_pCurrentSetting || m_CursorArgIndex < 0 || m_CursorArgIndex >= (int)m_pBackend->m_ParsedCommandArgs.at(k: m_pCurrentSetting).size()) ? nullptr : m_pBackend->m_ParsedCommandArgs.at(k: m_pCurrentSetting).at(n: m_CursorArgIndex).m_aName; }
246 float CurrentArgPos() const { return (m_CursorArgIndex < 0 || m_CursorArgIndex >= (int)m_vCurrentArgs.size()) ? 0 : m_vCurrentArgs[m_CursorArgIndex].m_X; }
247 size_t CurrentArgOffset() const { return m_CursorArgIndex == -1 ? 0 : m_vCurrentArgs[m_CursorArgIndex].m_Start; }
248 const char *CurrentArgValue() const { return m_CursorArgIndex == -1 ? m_aCommand : m_vCurrentArgs[m_CursorArgIndex].m_aValue; }
249 const std::vector<SPossibleValueMatch> &PossibleMatches() const { return m_vPossibleMatches; }
250 bool HasError() const { return m_Error.m_aMessage[0] != '\0'; }
251 size_t ErrorOffset() const { return m_Error.m_ArgIndex < 0 ? 0 : m_vCurrentArgs.at(n: m_Error.m_ArgIndex).m_Start; }
252 const char *Error() const { return m_Error.m_aMessage; }
253 int ArgCount() const { return (int)m_vCurrentArgs.size(); }
254 const SCurrentSettingArg &Arg(int Index) const { return m_vCurrentArgs.at(n: Index); }
255 const std::shared_ptr<IMapSetting> &Setting() const { return m_pCurrentSetting; }
256 void SetFontSize(float FontSize) { m_FontSize = FontSize; }
257 int CommentOffset() const { return m_CommentOffset; }
258
259 int CheckCollision(ECollisionCheckResult &Result) const;
260 int CheckCollision(const std::vector<CEditorMapSetting> &vSettings, ECollisionCheckResult &Result) const;
261 int CheckCollision(const char *pInputString, const std::vector<CEditorMapSetting> &vSettings, ECollisionCheckResult &Result) const;
262
263 void Update();
264 void UpdateFromString(const char *pStr);
265 bool UpdateCursor(bool Force = false);
266 virtual void Reset();
267 void GetCommandHelpText(char *pStr, int Length) const;
268 bool Valid() const;
269 void ColorArguments(std::vector<STextColorSplit> &vColorSplits) const;
270
271 bool m_AllowUnknownCommands;
272 SEditBoxDropdownContext m_DropdownContext;
273 int m_CurrentCompletionIndex;
274
275 private:
276 // Methods
277 CContext(CMapSettingsBackend *pBackend, CLineInput *pLineInput) :
278 m_DropdownContext(),
279 m_pBackend(pBackend)
280 {
281 m_AllowUnknownCommands = false;
282 m_pLineInput = pLineInput;
283 Reset();
284 }
285
286 void ClearError();
287 EValidationResult ValidateArg(int Index, const char *pArg);
288 void UpdatePossibleMatches();
289 void ParseArgs(const char *pLineInputStr, const char *pStr);
290 bool OnInput(const IInput::CEvent &Event);
291 const char *InputString() const;
292
293 template<int N>
294 void FormatDisplayValue(const char *pValue, char (&aOut)[N]);
295
296 // Fields
297 std::shared_ptr<IMapSetting> m_pCurrentSetting; // Current setting, can be nullptr in case of invalid setting name
298 std::vector<SCurrentSettingArg> m_vCurrentArgs; // Current parsed arguments from lineinput string
299 int m_CursorArgIndex; // The current argument the cursor is over
300 std::vector<SPossibleValueMatch> m_vPossibleMatches; // The current matches from cursor argument
301 size_t m_LastCursorOffset; // Last cursor offset
302 CLineInput *m_pLineInput;
303 char m_aCommand[128]; // The current command, not necessarily valid
304 SCommandParseError m_Error; // Error
305 int m_CommentOffset; // Position of the comment, if there is one
306
307 CMapSettingsBackend *m_pBackend;
308 float m_FontSize;
309 };
310
311 class CContextWithInput : public CContext
312 {
313 public:
314 CContextWithInput(CMapSettingsBackend *pBackend) :
315 CContext(pBackend, &m_CommandInput)
316 {
317 }
318
319 void Reset() override;
320
321 int m_CommandSelectedIndex;
322 CLineInputBuffered<256> m_CommandInput;
323 CListBox m_ListBox;
324 };
325 class CHeadlessContext : public CContext
326 {
327 public:
328 CHeadlessContext(CMapSettingsBackend *pBackend) :
329 CContext(pBackend, nullptr)
330 {
331 }
332 };
333
334 CContextWithInput NewContextWithInput()
335 {
336 return CContextWithInput(this);
337 }
338
339 CHeadlessContext NewHeadlessContext()
340 {
341 return CHeadlessContext(this);
342 }
343
344private:
345 // Loader methods
346 void LoadAllMapSettings();
347 void LoadCommand(const char *pName, const char *pArgs, const char *pHelp);
348 void LoadSettingInt(const std::shared_ptr<SMapSettingInt> &pSetting);
349 void LoadSettingCommand(const std::shared_ptr<SMapSettingCommand> &pSetting);
350 void InitValueLoaders();
351 void LoadPossibleValues(const CSettingValuesBuilder &Builder, const std::shared_ptr<IMapSetting> &pSetting);
352 void RegisterLoader(const char *pSettingName, const FLoaderFunction &pfnLoader);
353 void LoadConstraints();
354
355 static void PossibleConfigVariableCallback(const struct SConfigVariable *pVariable, void *pUserData);
356
357 // Argument constraints
358 using TArgumentConstraints = std::map<int, EArgConstraint>; // Constraint per argument index
359 using TCommandArgumentConstraints = std::map<std::string, TArgumentConstraints>; // Constraints per command/setting name
360
361 // Argument constraints builder
362 // Used to define arguments constraints for specific commands
363 // It uses a container stored in CMapSettingsBackend.
364 // Usage:
365 // CCommandArgumentConstraintBuilder Command(&m_Container);
366 // Command("tune", 2).Unique(0); // Defines argument 0 of command "tune" having 2 args as UNIQUE
367 // Command("tune_zone", 3).Multiple(0).Unique(1);
368 // // ^ Multiple() currently is only for readable purposes. It can be omitted:
369 // // Command("tune_zone", 3).Unique(1);
370 //
371
372 class CCommandArgumentConstraintBuilder;
373
374 class CArgumentConstraintsBuilder
375 {
376 friend class CCommandArgumentConstraintBuilder;
377
378 private:
379 CArgumentConstraintsBuilder(TArgumentConstraints *pContainer) :
380 m_pContainer(pContainer) {}
381
382 TArgumentConstraints *m_pContainer;
383
384 public:
385 CArgumentConstraintsBuilder &Multiple(int Arg)
386 {
387 // Define a multiple argument constraint
388 (*m_pContainer)[Arg] = EArgConstraint::MULTIPLE;
389 return *this;
390 }
391
392 CArgumentConstraintsBuilder &Unique(int Arg)
393 {
394 // Define a unique argument constraint
395 (*m_pContainer)[Arg] = EArgConstraint::UNIQUE;
396 return *this;
397 }
398 };
399
400 class CCommandArgumentConstraintBuilder
401 {
402 public:
403 CCommandArgumentConstraintBuilder(TCommandArgumentConstraints *pContainer) :
404 m_pContainer(pContainer) {}
405
406 CArgumentConstraintsBuilder operator()(const char *pSettingName, int ArgCount)
407 {
408 for(int i = 0; i < ArgCount; i++)
409 (*m_pContainer)[pSettingName][i] = EArgConstraint::DEFAULT;
410 return CArgumentConstraintsBuilder(&(*m_pContainer)[pSettingName]);
411 }
412
413 private:
414 TCommandArgumentConstraints *m_pContainer;
415 };
416
417 TCommandArgumentConstraints m_ArgConstraintsPerCommand;
418
419 // Backend fields
420 std::vector<std::shared_ptr<IMapSetting>> m_vpMapSettings;
421 std::map<std::shared_ptr<IMapSetting>, std::vector<SParsedMapSettingArg>> m_ParsedCommandArgs; // Parsed available settings arguments, used for validation
422 TSettingsArgumentValues m_PossibleValuesPerCommand;
423 std::map<std::string, FLoaderFunction> m_LoaderFunctions;
424
425 friend class CEditor;
426
427 // Map settings validation on load
428 struct SLoadedMapSettings
429 {
430 std::vector<SInvalidSetting> m_vSettingsInvalid;
431 std::vector<CEditorMapSetting> m_vSettingsValid;
432 std::map<int, std::vector<int>> m_SettingsDuplicate;
433
434 SLoadedMapSettings() = default;
435
436 void Reset()
437 {
438 m_vSettingsInvalid.clear();
439 m_vSettingsValid.clear();
440 m_SettingsDuplicate.clear();
441 }
442
443 } m_LoadedMapSettings;
444};
445
446#endif
447