/// <summary> /// componentセクションを書き出す /// </summary> /// <param name="component">小研究データ</param> /// <param name="writer">ファイル書き込み用</param> private static void WriteComponent(TechComponent component, StreamWriter writer) { writer.WriteLine(" # {0}", Config.ExistsKey(component.Name) ? Config.GetText(component.Name) : ""); writer.Write( " component = {{ id = {0} name = {1} type = {2} difficulty = {3}", component.Id, component.Name, Techs.SpecialityStrings[(int)component.Speciality], component.Difficulty); if (component.DoubleTime) { writer.Write(" double_time = yes"); } writer.WriteLine(" }"); }
/// <summary> /// componentセクションを構文解析する /// </summary> /// <param name="lexer">字句解析器</param> /// <returns>小研究データ</returns> private static TechComponent ParseComponent(TextLexer lexer) { // = Token token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); return(null); } // { token = lexer.GetToken(); if (token.Type != TokenType.OpenBrace) { Log.InvalidToken(LogCategory, token, lexer); return(null); } TechComponent component = new TechComponent(); while (true) { token = lexer.GetToken(); // ファイルの終端 if (token == null) { break; } // } (セクション終端) if (token.Type == TokenType.CloseBrace) { break; } // 無効なトークン if (token.Type != TokenType.Identifier) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } string keyword = token.Value as string; if (string.IsNullOrEmpty(keyword)) { continue; } keyword = keyword.ToLower(); // id if (keyword.Equals("id")) { // = token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン token = lexer.GetToken(); if (token.Type != TokenType.Number) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 小研究ID component.Id = (int)(double)token.Value; continue; } // name if (keyword.Equals("name")) { // = token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン token = lexer.GetToken(); if (token.Type != TokenType.Identifier && token.Type != TokenType.String) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 小研究名 component.Name = token.Value as string; continue; } // type if (keyword.Equals("type")) { // = token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン token = lexer.GetToken(); if (token.Type != TokenType.Identifier) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効な研究特性文字列 string s = token.Value as string; if (string.IsNullOrEmpty(s)) { continue; } s = s.ToLower(); if (!Techs.SpecialityStringMap.ContainsKey(s)) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 小研究特性 component.Speciality = Techs.SpecialityStringMap[s]; continue; } // difficulty if (keyword.Equals("difficulty")) { // = token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン token = lexer.GetToken(); if (token.Type != TokenType.Number) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 難易度 component.Difficulty = (int)(double)token.Value; continue; } // double_time if (keyword.Equals("double_time")) { // = token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン token = lexer.GetToken(); if (token.Type != TokenType.Identifier) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } string s = token.Value as string; if (string.IsNullOrEmpty(s)) { continue; } s = s.ToLower(); if (s.Equals("yes")) { // 倍の時間を要するかどうか component.DoubleTime = true; continue; } if (s.Equals("no")) { // 倍の時間を要するかどうか component.DoubleTime = false; continue; } // 無効なトークン Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); } return(component); }
/// <summary> /// applicationセクションを構文解析する /// </summary> /// <param name="lexer">字句解析器</param> /// <returns>技術データ</returns> private static TechItem ParseApplication(TextLexer lexer) { // = Token token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); return(null); } // { token = lexer.GetToken(); if (token.Type != TokenType.OpenBrace) { Log.InvalidToken(LogCategory, token, lexer); return(null); } TechItem application = new TechItem(); while (true) { token = lexer.GetToken(); // ファイルの終端 if (token == null) { break; } // } (セクション終端) if (token.Type == TokenType.CloseBrace) { break; } // 無効なトークン if (token.Type != TokenType.Identifier) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } string keyword = token.Value as string; if (string.IsNullOrEmpty(keyword)) { continue; } keyword = keyword.ToLower(); // id if (keyword.Equals("id")) { // = token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン token = lexer.GetToken(); if (token.Type != TokenType.Number) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 技術ID application.Id = (int)(double)token.Value; continue; } // name if (keyword.Equals("name")) { // = token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン token = lexer.GetToken(); if (token.Type != TokenType.Identifier && token.Type != TokenType.String) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 技術名 application.Name = token.Value as string; // 短縮名 application.ShortName = "SHORT_" + application.Name; continue; } // desc if (keyword.Equals("desc")) { // = token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン token = lexer.GetToken(); if (token.Type != TokenType.Identifier && token.Type != TokenType.String) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 技術説明 application.Desc = token.Value as string; continue; } // position if (keyword.Equals("position")) { TechPosition position = ParsePosition(lexer); if (position == null) { Log.InvalidSection(LogCategory, "position", lexer); continue; } // 座標リスト application.Positions.Add(position); continue; } // picture if (keyword.Equals("picture")) { // = token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン token = lexer.GetToken(); if (token.Type != TokenType.String) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 画像ファイル名 application.PictureName = token.Value as string; continue; } // year if (keyword.Equals("year")) { // = token = lexer.GetToken(); if (token.Type != TokenType.Equal) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 無効なトークン token = lexer.GetToken(); if (token.Type != TokenType.Number) { Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); continue; } // 史実年 application.Year = (int)(double)token.Value; continue; } // component if (keyword.Equals("component")) { TechComponent component = ParseComponent(lexer); if (component == null) { Log.InvalidSection(LogCategory, "component", lexer); continue; } // 小研究 application.Components.Add(component); continue; } // required if (keyword.Equals("required")) { IEnumerable <int> ids = ParseRequired(lexer); if (ids == null) { Log.InvalidSection(LogCategory, "required", lexer); continue; } // 必要とする技術群(AND) foreach (int id in ids) { RequiredTech tech = new RequiredTech { Id = id }; application.AndRequiredTechs.Add(tech); } continue; } // or_required if (keyword.Equals("or_required")) { IEnumerable <int> ids = ParseRequired(lexer); if (ids == null) { Log.InvalidSection(LogCategory, "or_required", lexer); continue; } // 必要とする技術群(OR) foreach (int id in ids) { RequiredTech tech = new RequiredTech { Id = id }; application.OrRequiredTechs.Add(tech); } continue; } // effects if (keyword.Equals("effects")) { IEnumerable <Command> commands = ParseEffects(lexer); if (commands == null) { Log.InvalidSection(LogCategory, "effects", lexer); continue; } // 技術効果 application.Effects.AddRange(commands); continue; } // 無効なトークン Log.InvalidToken(LogCategory, token, lexer); lexer.SkipLine(); } return(application); }
/// <summary> /// 研究速度の基本進捗率を取得する /// </summary> /// <param name="component">小研究</param> /// <param name="team">研究機関</param> /// <returns>研究速度の基本進捗率</returns> private static double GetBaseProgress(TechComponent component, Team team) { int d = component.Difficulty + 2; int s = team.Skill; // 特性が一致する場合はスキルを倍にして6を加える if (team.Specialities.Contains(component.Speciality)) { s += team.Skill + 6; } // 研究施設がある場合は施設の規模を設定する int t = 0; switch (component.Speciality) { case TechSpeciality.Rocketry: t = Researches.RocketTestingSites; break; case TechSpeciality.NuclearPhysics: case TechSpeciality.NuclearEngineering: t = Researches.NuclearReactors; break; } // ゲームごとの基本進捗率を取得する double progress; switch (Game.Type) { case GameType.HeartsOfIron2: progress = (9.3 + 1.5 * s + 10 * t) / d; break; case GameType.ArsenalOfDemocracy: progress = (3.0 + 0.5 * (s + 10 * Math.Sqrt(t))) / d; break; case GameType.DarkestHour: progress = (9.0 + 1.5 * (s + 5.62 * t)) / d; break; default: // ゲームの種類が不明な場合はHoI2として扱う progress = (9.3 + 1.5 * s + 10 * t) / d; break; } // 2倍時間設定の場合は進捗率半分として扱う if (component.DoubleTime) { progress /= 2; } // 青写真補正 if (Researches.Blueprint) { progress *= Misc.BlueprintBonus; } // AoDの場合Miscの補正を考慮 if (Game.Type == GameType.ArsenalOfDemocracy) { progress *= Misc.TechSpeedModifier; } // その他諸々の補正(計算機技術/閣僚/難易度/シナリオ設定など) progress *= Researches.Modifier; return(progress); }
/// <summary> /// 小研究に必要な日数を取得する /// </summary> /// <param name="component">小研究</param> /// <param name="offset">史実年度との差分日数</param> /// <param name="team">研究機関</param> /// <returns>日数</returns> private static int GetComponentDays(TechComponent component, int offset, Team team) { int totalDays = 0; double totalProgress = 0; // 基準となる進捗率を求める double baseProgress = GetBaseProgress(component, team); // STEP1: 史実年度前ペナルティ下限値で研究する日数を求める if ((offset < 0) && (Misc.PreHistoricalDateModifier < 0)) { // 史実年度前ペナルティを求める double preHistoricalModifier = Misc.PreHistoricalDateModifier; // 史実年度前ペナルティの下限値を求める double preHistoricalLimit = Game.Type == GameType.ArsenalOfDemocracy ? Misc.PreHistoricalPenaltyLimit : 0.1; // 史実年度前ペナルティが下限値になる日を求める int preHistoricalLimitOffset = (int)Math.Floor((1 - preHistoricalLimit) / preHistoricalModifier); // 差分日数が下限値日数を超える場合 if (offset <= preHistoricalLimitOffset) { // 下限値で研究完了する日数を求める int preHistoricalLimitDays = (int)Math.Ceiling(100 / (baseProgress * preHistoricalLimit)); // 下限値で研究完了する場合は日数を返す if (offset + preHistoricalLimitDays <= preHistoricalLimitOffset) { return(preHistoricalLimitDays); } // 下限値で研究する日数と進捗を加算する preHistoricalLimitDays = preHistoricalLimitOffset - offset; totalDays = preHistoricalLimitDays; totalProgress = baseProgress * preHistoricalLimit * preHistoricalLimitDays; offset += preHistoricalLimitDays; } } // STEP2: 下限未達の史実年度前ペナルティで研究する日数を求める if (offset < 0) { // 史実年度前ペナルティを求める double preHistoricalModifier = Misc.PreHistoricalDateModifier; // 史実年度前ペナルティありで研究する日数を求める int preHistricalDays = GetPreHistoricalDays(baseProgress, 100 - totalProgress, offset, preHistoricalModifier); // 史実年度前に研究完了する場合は日数を返す if (offset + preHistricalDays <= 0) { totalDays += preHistricalDays; return(totalDays); } // 史実年度前に研究する日数と進捗を加算する preHistricalDays = -offset; totalDays += preHistricalDays; totalProgress += GetPreHistoricalProgress(baseProgress, preHistricalDays, offset, preHistoricalModifier); offset = 0; } // STEP3: 史実年度前後の補正がない状態で研究する日数を求める // HoI2の場合史実年度後補正がない if (Game.Type == GameType.HeartsOfIron2) { totalDays += (int)Math.Ceiling((100 - totalProgress) / baseProgress); return(totalDays); } // 史実年度後ボーナスを求める double postHistoricalModifier = Game.Type == GameType.ArsenalOfDemocracy ? Misc.PostHistoricalDateModifierAoD : Misc.PostHistoricalDateModifierDh; // 史実年度後ボーナスがない場合 if (postHistoricalModifier <= 0) { totalDays += (int)Math.Ceiling((100 - totalProgress) / baseProgress); return(totalDays); } // DHの場合、1年経過までは補正なし if (Game.Type == GameType.DarkestHour) { // ロケット技術/核技術には史実年度後ボーナスが適用されない switch (component.Speciality) { case TechSpeciality.Rocketry: case TechSpeciality.NuclearPhysics: case TechSpeciality.NuclearEngineering: totalDays += (int)Math.Ceiling((100 - totalProgress) / baseProgress); return(totalDays); } offset -= 360; if (offset < 0) { // 史実年度後ボーナスなしで研究する日数を求める int historicalDays = (int)Math.Ceiling((100 - totalProgress) / baseProgress); // 史実年度後ボーナスなしの機関に研究が完了する場合 if (offset + historicalDays < 0) { totalDays += historicalDays; return(totalDays); } // 史実年度後ボーナスなしで研究する日数と進捗を加算する historicalDays = -offset; totalDays += historicalDays; totalProgress += baseProgress * historicalDays; offset = 0; } } // STEP4: 上限未達の史実年度後ボーナスで研究する日数を求める // 史実年度後ボーナスの上限値を求める double postHistoricalLimit = Game.Type == GameType.ArsenalOfDemocracy ? Misc.PostHistoricalBonusLimit : Misc.BlueprintBonus; // 史実年度後ボーナスが上限値になる日を求める int postHistoricalLimitOffset = (int)Math.Ceiling(Math.Abs((postHistoricalLimit - 1) / postHistoricalModifier)); if (offset < postHistoricalLimitOffset) { // 史実年度後ボーナスありで研究する日数を求める int postHistoricalDays = GetPostHistoricalDays(baseProgress, 100 - totalProgress, offset, postHistoricalModifier); // 史実年度後ボーナスが上限値に到達する前に研究が完了する場合 if (offset + postHistoricalDays < postHistoricalLimitOffset) { totalDays += postHistoricalDays; return(totalDays); } // 史実年度後に研究する日数と進捗を加算する postHistoricalDays = postHistoricalLimitOffset - offset - 1; totalDays += postHistoricalDays; totalProgress += GetPostHistoricalProgress(baseProgress, postHistoricalDays, offset, postHistoricalModifier); } // STEP5: 史実年度後ボーナス上限値で研究する日数を求める totalDays += (int)Math.Ceiling((100 - totalProgress) / (baseProgress * postHistoricalLimit)); return(totalDays); }