mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-09-09 15:37:05 +00:00
* feat: IW3 menu dumping * fix: IW3 menu dumper preserve menu ownerdraw flag masks * fix: IW3 menu dumper preserve numeric script arguments Emit numeric tokens without quotes so the linker keeps colour arguments separate. Before: `"0.1" "0.1" "0.12" "0.5"` becomes `"0.10.10.120.5"` After: `0.1 0.1 0.12 0.5` This fixes `mouseExit` colour commands failing to clear menu hover borders. * fix: IW3 menu dumper preserve empty item text Keep explicit empty text distinct from null text when dumping menu items. This fixes CD-key values overflowing their input boxes. * test: cover IW3 menu dumper material case * test: cover IW3 menu list path precedence * chore: clang format * fix: reverse order of union to avoid edge case bug in zcg * feat: IW3 menu loading * fix: validate menu expression name table * chore: explain numeric token quoting in comment * refactor: simplify menu dump fallback path * chore: add more dynamic window flags * refactor: reuse IW3 menu window flag constants * refactor: use explicit menu item feature lookup * refactor: represent menu item text as optional * refactor: clarify shared IW3 and IW4 menu constants IW4 only extends the type enum so they can be shared * refactor: clarify IW3 menu zone state naming * fix: log IW3 menu conversion failures * refactor: align IW3 menu list loader with IW4 * refactor: align IW3 menu dumpers with IW4 * refactor: align IW3 menu converter with IW4 * chore: use default enum numeration for operationEnum * chore: small optimization on edit field capability initialization * fix: iw3 menu optimizations for rect not considering relative position to parent * chore: align iw3 menu converting closer to iw4 * chore: align iw3 menu dumping closer to iw4 * fix: not dumping menu flags properties correctly * chore: adjust iw3 expression dumping to work similar to iw4 * chore: adjust iw3 expression converting to work similar to iw4 * chore: use unordered_map for MenuExpressionMatchers --------- Co-authored-by: Jan Laupetin <[email protected]>
312 lines
8.8 KiB
C++
312 lines
8.8 KiB
C++
#include "AbstractMenuWriter.h"
|
|
|
|
#include "Parsing/Impl/ParserSingleInputStream.h"
|
|
#include "Parsing/Simple/SimpleLexer.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <sstream>
|
|
|
|
namespace menu
|
|
{
|
|
AbstractBaseWriter::AbstractBaseWriter(std::ostream& stream)
|
|
: m_stream(stream),
|
|
m_indent(0u)
|
|
{
|
|
}
|
|
|
|
void AbstractBaseWriter::IncIndent()
|
|
{
|
|
m_indent++;
|
|
}
|
|
|
|
void AbstractBaseWriter::DecIndent()
|
|
{
|
|
if (m_indent > 0)
|
|
m_indent--;
|
|
}
|
|
|
|
void AbstractBaseWriter::Indent() const
|
|
{
|
|
for (auto i = 0u; i < m_indent; i++)
|
|
m_stream << " ";
|
|
}
|
|
|
|
void AbstractBaseWriter::StartScope(const std::string& scopeName)
|
|
{
|
|
Indent();
|
|
m_stream << scopeName << "\n";
|
|
Indent();
|
|
m_stream << "{\n";
|
|
IncIndent();
|
|
}
|
|
|
|
void AbstractBaseWriter::StartMenuDefScope()
|
|
{
|
|
StartScope("menuDef");
|
|
}
|
|
|
|
void AbstractBaseWriter::StartItemDefScope()
|
|
{
|
|
StartScope("itemDef");
|
|
}
|
|
|
|
void AbstractBaseWriter::StartFunctionDefScope()
|
|
{
|
|
StartScope("functionDef");
|
|
}
|
|
|
|
void AbstractBaseWriter::EndScope()
|
|
{
|
|
DecIndent();
|
|
Indent();
|
|
m_stream << "}\n";
|
|
}
|
|
|
|
std::vector<std::string> AbstractBaseWriter::CreateScriptTokenList(const char* script)
|
|
{
|
|
const std::string scriptString(script);
|
|
std::istringstream stringStream(scriptString);
|
|
ParserSingleInputStream inputStream(stringStream, "MenuScript");
|
|
|
|
SimpleLexer::Config lexerConfig;
|
|
lexerConfig.m_emit_new_line_tokens = false;
|
|
lexerConfig.m_read_strings = true;
|
|
lexerConfig.m_string_escape_sequences = true;
|
|
lexerConfig.m_read_integer_numbers = false;
|
|
lexerConfig.m_read_floating_point_numbers = false;
|
|
SimpleLexer lexer(&inputStream, std::move(lexerConfig));
|
|
|
|
std::vector<std::string> result;
|
|
auto hasLexerTokens = true;
|
|
while (hasLexerTokens)
|
|
{
|
|
const auto& token = lexer.GetToken(0);
|
|
switch (token.m_type)
|
|
{
|
|
case SimpleParserValueType::IDENTIFIER:
|
|
result.emplace_back(token.IdentifierValue());
|
|
break;
|
|
|
|
case SimpleParserValueType::STRING:
|
|
result.emplace_back(token.StringValue());
|
|
break;
|
|
|
|
case SimpleParserValueType::CHARACTER:
|
|
result.emplace_back(1, token.CharacterValue());
|
|
break;
|
|
|
|
case SimpleParserValueType::INVALID:
|
|
case SimpleParserValueType::END_OF_FILE:
|
|
hasLexerTokens = false;
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
lexer.PopTokens(1);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool AbstractBaseWriter::DoesTokenNeedQuotationMarks(const std::string& token)
|
|
{
|
|
if (token.empty())
|
|
return true;
|
|
|
|
// The menu linker concatenates adjacent quoted numeric arguments. For example,
|
|
// `"0.1" "0.1" "0.12" "0.5"` becomes `"0.10.10.120.5"` instead of four
|
|
// color components.
|
|
char* numericEnd;
|
|
(void)std::strtof(token.c_str(), &numericEnd);
|
|
if (numericEnd == token.c_str() + token.size())
|
|
return false;
|
|
|
|
const auto hasAlNumCharacter = std::ranges::any_of(token,
|
|
[](const char& c)
|
|
{
|
|
return isalnum(c);
|
|
});
|
|
|
|
if (!hasAlNumCharacter)
|
|
return false;
|
|
|
|
const auto hasNonIdentifierCharacter = std::ranges::any_of(token,
|
|
[](const char& c)
|
|
{
|
|
return !isalnum(c) && c != '_';
|
|
});
|
|
|
|
return hasNonIdentifierCharacter;
|
|
}
|
|
|
|
void AbstractBaseWriter::WriteEscapedString(const std::string_view& str) const
|
|
{
|
|
m_stream << "\"";
|
|
|
|
for (const auto& c : str)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '\r':
|
|
m_stream << "\\r";
|
|
break;
|
|
case '\n':
|
|
m_stream << "\\n";
|
|
break;
|
|
case '\t':
|
|
m_stream << "\\t";
|
|
break;
|
|
case '\f':
|
|
m_stream << "\\f";
|
|
break;
|
|
case '"':
|
|
m_stream << "\\\"";
|
|
break;
|
|
default:
|
|
m_stream << c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_stream << "\"";
|
|
}
|
|
|
|
const std::string& AbstractBaseWriter::BoolValue(const bool value)
|
|
{
|
|
return value ? BOOL_VALUE_TRUE : BOOL_VALUE_FALSE;
|
|
}
|
|
|
|
void AbstractBaseWriter::WriteKey(const std::string& keyName) const
|
|
{
|
|
m_stream << keyName;
|
|
|
|
if (keyName.size() < MENU_KEY_SPACING)
|
|
{
|
|
const auto spacingLength = MENU_KEY_SPACING - keyName.size();
|
|
for (auto i = 0u; i < spacingLength; i++)
|
|
m_stream << " ";
|
|
}
|
|
}
|
|
|
|
void AbstractBaseWriter::WriteStringProperty(const std::string& propertyKey, const std::string& propertyValue) const
|
|
{
|
|
if (propertyValue.empty())
|
|
return;
|
|
|
|
Indent();
|
|
WriteKey(propertyKey);
|
|
|
|
WriteEscapedString(propertyValue);
|
|
m_stream << "\n";
|
|
}
|
|
|
|
void AbstractBaseWriter::WriteStringProperty(const std::string& propertyKey, const char* propertyValue) const
|
|
{
|
|
if (propertyValue == nullptr || propertyValue[0] == '\0')
|
|
return;
|
|
|
|
Indent();
|
|
WriteKey(propertyKey);
|
|
|
|
WriteEscapedString(propertyValue);
|
|
m_stream << "\n";
|
|
}
|
|
|
|
void AbstractBaseWriter::WriteBoolProperty(const std::string& propertyKey, const bool propertyValue, const bool defaultValue) const
|
|
{
|
|
if (propertyValue == defaultValue)
|
|
return;
|
|
|
|
Indent();
|
|
WriteKey(propertyKey);
|
|
m_stream << BoolValue(propertyValue) << "\n";
|
|
}
|
|
|
|
void AbstractBaseWriter::WriteIntProperty(const std::string& propertyKey, const int propertyValue, const int defaultValue) const
|
|
{
|
|
if (propertyValue == defaultValue)
|
|
return;
|
|
|
|
Indent();
|
|
WriteKey(propertyKey);
|
|
m_stream << propertyValue << "\n";
|
|
}
|
|
|
|
void AbstractBaseWriter::WriteFloatProperty(const std::string& propertyKey, const float propertyValue, const float defaultValue) const
|
|
{
|
|
if (std::fabs(propertyValue - defaultValue) < std::numeric_limits<float>::epsilon())
|
|
return;
|
|
|
|
Indent();
|
|
WriteKey(propertyKey);
|
|
m_stream << propertyValue << "\n";
|
|
}
|
|
|
|
void AbstractBaseWriter::WriteColorProperty(const std::string& propertyKey, const float (&propertyValue)[4], const float (&defaultValue)[4]) const
|
|
{
|
|
if (std::fabs(propertyValue[0] - defaultValue[0]) < std::numeric_limits<float>::epsilon()
|
|
&& std::fabs(propertyValue[1] - defaultValue[1]) < std::numeric_limits<float>::epsilon()
|
|
&& std::fabs(propertyValue[2] - defaultValue[2]) < std::numeric_limits<float>::epsilon()
|
|
&& std::fabs(propertyValue[3] - defaultValue[3]) < std::numeric_limits<float>::epsilon())
|
|
{
|
|
return;
|
|
}
|
|
|
|
Indent();
|
|
WriteKey(propertyKey);
|
|
m_stream << propertyValue[0] << " " << propertyValue[1] << " " << propertyValue[2] << " " << propertyValue[3] << "\n";
|
|
}
|
|
|
|
void AbstractBaseWriter::WriteKeywordProperty(const std::string& propertyKey, const bool shouldWrite) const
|
|
{
|
|
if (!shouldWrite)
|
|
return;
|
|
|
|
Indent();
|
|
WriteKey(propertyKey);
|
|
m_stream << "\n";
|
|
}
|
|
|
|
void AbstractBaseWriter::WriteFlagsProperty(const std::string& propertyKey, const unsigned flagsValue) const
|
|
{
|
|
for (auto i = 0u; i < sizeof(flagsValue) * 8; i++)
|
|
{
|
|
const unsigned mask = 1u << i;
|
|
if (flagsValue & mask)
|
|
{
|
|
Indent();
|
|
WriteKey(propertyKey);
|
|
m_stream << mask << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void AbstractBaseWriter::Start()
|
|
{
|
|
Indent();
|
|
m_stream << "{\n";
|
|
IncIndent();
|
|
}
|
|
|
|
void AbstractBaseWriter::End()
|
|
{
|
|
for (auto i = 0u; i < m_indent; i++)
|
|
{
|
|
DecIndent();
|
|
Indent();
|
|
m_stream << "}\n";
|
|
}
|
|
}
|
|
|
|
void AbstractBaseWriter::IncludeMenu(const std::string& menuPath) const
|
|
{
|
|
Indent();
|
|
m_stream << "loadMenu { \"" << menuPath << "\" }\n";
|
|
}
|
|
} // namespace menu
|