feat: 초기 커밋

- Progress Quest 6.4 Flutter 포팅 프로젝트
- 게임 루프, 상태 관리, UI 구현
- 캐릭터 생성, 인벤토리, 장비, 주문 시스템
- 시장/판매/구매 메커니즘
This commit is contained in:
JiWoong Sul
2025-12-09 17:24:04 +09:00
commit 08054d97c1
168 changed files with 12876 additions and 0 deletions

145
tool/dfm_extract.dart Normal file
View File

@@ -0,0 +1,145 @@
// Extracts TMemo Lines.Strings from example/pq/Config.dfm into a Dart const map.
// Usage:
// dart run tool/dfm_extract.dart [input_dfm] [output_dart]
// Defaults:
// input_dfm: example/pq/Config.dfm
// output_dart: lib/data/pq_config_data.dart
import 'dart:convert';
import 'dart:io';
void main(List<String> args) {
final inputPath = args.isNotEmpty ? args[0] : 'example/pq/Config.dfm';
final outputPath = args.length > 1 ? args[1] : 'lib/data/pq_config_data.dart';
final file = File(inputPath);
if (!file.existsSync()) {
stderr.writeln('Input file not found: $inputPath');
exitCode = 1;
return;
}
final lines = file.readAsLinesSync();
final data = _parseMemos(lines);
_writeDart(outputPath, data);
_printSummary(outputPath, data);
}
Map<String, List<String>> _parseMemos(List<String> lines) {
final result = <String, List<String>>{};
String? currentMemo;
bool inLines = false;
for (final rawLine in lines) {
final line = rawLine.trimRight();
final trimmed = line.trimLeft();
// Detect TMemo object names.
if (trimmed.startsWith('object ') && trimmed.contains(': TMemo')) {
final token = trimmed.split(' ')[1];
currentMemo = token.contains(':') ? token.split(':').first : token;
inLines = false;
continue;
}
// Detect start of Lines.Strings block.
if (trimmed.contains('Lines.Strings')) {
if (currentMemo == null) continue;
inLines = true;
result[currentMemo] = <String>[];
continue;
}
if (!inLines || currentMemo == null) {
continue;
}
// End of block.
if (trimmed == ')') {
inLines = false;
continue;
}
// Extract string literal line (may end with "')" on last item).
final hasClosing = trimmed.endsWith(')');
final value = _decodeDelphiLiteral(line, stripTrailingParen: hasClosing);
if (value.isNotEmpty) {
result[currentMemo]!.add(value);
}
// Handle closing parenthesis on same line.
if (hasClosing) {
inLines = false;
}
}
return result;
}
String _decodeDelphiLiteral(String line, {required bool stripTrailingParen}) {
// Remove trailing ')' only when it denotes end-of-block.
var work = line;
if (stripTrailingParen && work.trimRight().endsWith(')')) {
work = work.substring(0, work.lastIndexOf(')'));
}
final buffer = StringBuffer();
var i = 0;
while (i < work.length) {
final ch = work[i];
if (ch == '\'') {
// Quoted segment.
i++;
final start = i;
while (i < work.length && work[i] != '\'') {
i++;
}
buffer.write(work.substring(start, i));
i++; // Skip closing quote.
} else if (ch == '#') {
// Numeric char code segment.
i++;
final start = i;
while (i < work.length && _isDigit(work.codeUnitAt(i))) {
i++;
}
final code = int.parse(work.substring(start, i));
buffer.write(String.fromCharCode(code));
} else {
i++; // Ignore other characters between tokens.
}
}
return buffer.toString();
}
bool _isDigit(int charCode) => charCode >= 48 && charCode <= 57;
void _writeDart(String outputPath, Map<String, List<String>> data) {
final buffer = StringBuffer()
..writeln('// GENERATED CODE - DO NOT EDIT BY HAND.')
..writeln(
'// Generated by tool/dfm_extract.dart from example/pq/Config.dfm',
)
..writeln('')
..writeln('const Map<String, List<String>> pqConfigData = {');
final sortedKeys = data.keys.toList()..sort();
for (final key in sortedKeys) {
final encodedKey = jsonEncode(key);
final encodedList = jsonEncode(data[key]);
buffer.writeln(' $encodedKey: $encodedList,');
}
buffer.writeln('};');
File(outputPath)
..createSync(recursive: true)
..writeAsStringSync(buffer.toString());
}
void _printSummary(String outputPath, Map<String, List<String>> data) {
stdout.writeln('Wrote $outputPath');
for (final entry in data.entries) {
stdout.writeln(' - ${entry.key}: ${entry.value.length} items');
}
}