using Helpers;
using System;
using System.Collections.Generic;
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.Actions;
using TaleWorlds.CampaignSystem.CharacterDevelopment.Managers;
using TaleWorlds.CampaignSystem.SandBox;
using TaleWorlds.Core;
using TaleWorlds.Library;
using TaleWorlds.Localization;
using TaleWorlds.SaveSystem;
namespace ExampleIssues
{
public class VillageNeedsFoodBehavior : CampaignBehaviorBase
{
private const int VillageFoodThreshold = 10;
private const int TownFoodThreshold = 50;
private const int MinTroopTier = 1;
private const float IssueDuration = 30f;
private const float QuestTimeLimit = 10f;
private const float IssuePreConditionMinPlayerRelation = -10f;
public override void RegisterEvents()
{
CampaignEvents.OnCheckForIssueEvent.AddNonSerializedListener(this, OnCheckForIssue);
}
public override void SyncData(IDataStore dataStore)
{
}
// イベントリスナーです。
private void OnCheckForIssue(Hero hero)
{
// 詳しいことは分かりませんが、条件に合う Hero が見つかると
// 指定した頻度で Issue が生成されるという感じでしょうか。
if (ConditionsHold(hero))
{
Campaign.Current.IssueManager.AddPotentialIssueData(
hero,
new PotentialIssueData
.StartIssueDelegate(OnStartIssue
),
typeof(VillageNeedsFoodIssue
),
IssueBase.IssueFrequency.VeryCommon,
null));
}
else
{
Campaign.Current.IssueManager.AddPotentialIssueData(
hero,
typeof(VillageNeedsFoodIssue
),
IssueBase.IssueFrequency.VeryCommon));
}
}
// Hero が Issue を持つのに必要な条件を設定しています。
//
// 開始直後から困窮している場所など無いと思われるので、
// テストプレイ用に条件は甘くしてあります。
private bool ConditionsHold(Hero issueOwner)
{
// IssueOwner になれるのは Headman (村長) のみ
// IssueOwner は村に所属していなければならない
// その村の食糧は一定値未満でなければならない
Settlement currentSettlement = issueOwner.CurrentSettlement;
if (!issueOwner.IsHeadman
|| currentSettlement
is null || !currentSettlement.IsVillage
|| currentSettlement.ItemRoster.GetItemNumber(DefaultItems.Grain) >= VillageFoodThreshold)
{
return false;
}
// 町と取引していないか、町にも食糧が無い
Settlement tradeBound = currentSettlement.Village.TradeBound;
return tradeBound
is null || tradeBound
.ItemRoster.GetItemNumber(DefaultItems
.Grain) < TownFoodThreshold
; }
private IssueBase OnStartIssue(in PotentialIssueData pid, Hero issueOwner)
{
return new VillageNeedsFoodIssue
(issueOwner, DefaultItems
.Grain); }
// ----------------------------------------------------------------
// Issue に対する Behavior の定義はここまで。
//
// ここからは、カスタム型を保存するためのクラスです。
// ----------------------------------------------------------------
// カスタムビヘイビアーで使用する型を登録します。
public class VillageNeedsFoodBehaviorTypeDefiner : SaveableCampaignBehaviorTypeDefiner
{
// saveBaseId は、自分のビヘイビアー同士はもちろん、同時使用している他の MOD とも被ってはいけないそうです。
// また、番号を途中で変えるとセーブデータ読み込み時にクラッシュします。
// 番号付け規則は、追々コミュニティー内で意思統一が図られることでしょう。
public VillageNeedsFoodBehaviorTypeDefiner() : base(001_123_456)
{
}
// このクエストで使う独自のクラスを登録しています。
//
// 構造体型なら SaveableTypeDefiner.DefineStructTypes()
// 列挙型なら SaveableTypeDefiner.DefineEnumTypes()
// のようにメソッドが分かれています。
protected override void DefineClassTypes()
{
// こちらの saveId は、各 SaveableCampaignBehaviorTypeDefiner の中で被りが無ければいいようです。
AddClassDefinition
(typeof(VillageNeedsFoodIssue
),
1); AddClassDefinition
(typeof(VillageNeedsFoodIssueQuest
),
2); }
}
// ----------------------------------------------------------------
// カスタム型の宣言はここまで。
//
// ここからは Issue 本体の定義です。
// ----------------------------------------------------------------
internal class VillageNeedsFoodIssue : IssueBase
{
[SaveableField(10)] private readonly ItemObject _requestedFood;
// IssueBase.IssueBase() に渡す日数は、Issue が自然消滅するまでの日数です。
// クエストの達成期限と混同しないようにしてください。
public VillageNeedsFoodIssue(Hero issueOwner, ItemObject requestedFood) : base(issueOwner, CampaignTime.DaysFromNow(IssueDuration))
{
_requestedFood = requestedFood;
}
// Issue のタイトルです。
// 町などで Issue を抱えている人物のポートレートに表示されます。
// Quest のタイトルとは同じにも別にもできます。
//
// チートコードで IssueOwner を探せなくなるので、テストでは日本語名は避けた方がいいです。
public override TextObject Title
=> new TextObject
("Village Needs Food");
// Issue 内容の簡単な説明です。
// IssueOwner のポートレートで Title にマウスカーソルを合わせるとホバー表示されます。
public override TextObject Description
{
get
{
TextObject textObject
= new TextObject
("{ISSUE_OWNER.LINK} が食糧を届けるよう頼んでいる。"); StringHelpers.SetCharacterProperties("ISSUE_OWNER", IssueOwner.CharacterObject, textObject);
return textObject;
}
}
// 導入部のセリフのオーバーライドです。
public override TextObject IssueBriefByIssueGiver
{
get
{
TextObject textObject
= new TextObject
( "{FOOD} が乏しくなっているのですが、 " +
"近くの町でもあまり手に入らないのです。 " +
"このご時世、あまり遠方へ買い求めに行くことも " +
"できず、困っております。");
textObject.SetTextVariable("FOOD", _requestedFood.Name);
return textObject;
}
}
public override TextObject IssueAcceptByPlayer
=> new TextObject
( "何か私にできることはあるか?");
public override TextObject IssueQuestSolutionExplanationByIssueGiver
=> new TextObject
( "いずこかで買い求め、持ってきてはいただけませ " +
"んでしょうか? 報酬はお支払いいたします。");
public override TextObject IssueQuestSolutionAcceptByPlayer
=> new TextObject
( "わかった、やってみよう。");
// コンパニオンに解決を任せられるか否かです。
public override bool IsThereAlternativeSolution => true;
// Alternative Solution 用のセリフです。
public override TextObject IssueAlternativeSolutionExplanationByIssueGiver
=> new TextObject
( "あなた様のご都合が悪いようであれば、お側付き " +
"の方に届けていただくのでも構いません。");
public override TextObject IssueAlternativeSolutionAcceptByPlayer
=> new TextObject
( "ならば部下を遣わすとしよう。");
public override TextObject IssueAlternativeSolutionResponseByIssueGiver
=> new TextObject
( "それでは、よろしくお願いいたします。");
// Alternative Solution 用のログテキストです。
protected override TextObject AlternativeSolutionStartLog
{
get
{
TextObject textObject
= new TextObject
( "{SETTLEMENT.LINK} の {ISSUE_OWNER.LINK} に、 {REQUESTED_FOOD} を持ってくるよう頼まれた。 " +
"あなたは部下の {COMPANION.LINK} に{TROOP_COUNT}人の兵を預け、それら届けさせることにした。 " +
"{RETURN_DAYS}日後には任務を終え、戻ってくるだろう。 " +
"その際には、報酬として {PAYMENT}{GOLD_ICON} を手にしてくるはずだ。");
StringHelpers.SetSettlementProperties("SETTLEMENT", IssueOwner.CurrentSettlement, textObject);
StringHelpers.SetCharacterProperties("ISSUE_OWNER", IssueOwner.CharacterObject, textObject);
StringHelpers.SetCharacterProperties("COMPANION", AlternativeSolutionHero.CharacterObject, textObject);
textObject.SetTextVariable("REQUESTED_FOOD", _requestedFood.Name)
.SetTextVariable("TROOP_COUNT", AlternativeSolutionSentTroops.TotalManCount)
.SetTextVariable("RETURN_DAYS", GetTotalAlternativeSolutionDurationInDays())
.SetTextVariable("PAYMENT", RewardGold);
return textObject;
}
}
protected override TextObject AlternativeSolutionEndLogDefault
{
get
{
TextObject textObject
= new TextObject
( "{COMPANION.LINK} たちは {REQUESTED_FOOD} を無事に届けた。 " +
"{ISSUE_OWNER.LINK} は大いに喜び、約束通りの報酬を差し出した。");
StringHelpers.SetCharacterProperties("COMPANION", AlternativeSolutionHero.CharacterObject, textObject);
StringHelpers.SetCharacterProperties("ISSUE_OWNER", IssueOwner.CharacterObject, textObject);
textObject.SetTextVariable("REQUESTED_FOOD", _requestedFood.Name)
.SetTextVariable("PAYMENT", RewardGold);
return textObject;
}
}
// 領主として影響力を消費して解決できるか否かです。
public override bool IsThereLordSolution => false;
protected override int AlternativeSolutionNeededBaseMenCountInternal => (int)(9f + (14f * IssueDifficultyMultiplier));
protected override int AlternativeSolutionBaseDurationInDaysInternal => (int)(10f + (8f * IssueDifficultyMultiplier));
protected override int RewardGold => (int)(500f + (1000f * IssueDifficultyMultiplier));
protected override int CompanionSkillRewardXP => (int)(600f + (800f * IssueDifficultyMultiplier));
// コンパニオンに求められるスキル値です。
// しきい値を下回っているコンパニオンに任せると失敗する可能性が発生します。
public override List<SkillObject> GetAlternativeSolutionRequiredCompanionSkill(out int requiredSkillLevel)
{
requiredSkillLevel = CompanionSkillThreshold;
return new List
<SkillObject
> {
DefaultSkills.Roguery,
DefaultSkills.Trade,
};
}
// 要求される食糧の量です。
// クエスト難易度でスケーリングしています。
private int RequestedFoodAmount => (int)(15f + (30f * IssueDifficultyMultiplier));
private int CompanionSkillThreshold => 75;
// Issue が自動生成される頻度です。
// IssueFrequency.VeryCommon
// IssueFrequency.Common
// IssueFrequency.Rare
public override IssueFrequency GetFrequency()
{
return IssueFrequency.VeryCommon;
}
// Issue が消滅する条件を設定できます (StayAlive なので、正確には「消滅しないための条件」です)。
//
// 例えば、この Issue が「物資を依頼人とは別の人のもとに届ける」というような内容だった場合、
// 届け先の人物が何らかの理由でゲームから退場してしまったら、その時点で Issue は破棄されなければなりません。
// (もちろん、プレイヤーが既にクエストを請けていればクエストも中止されるべきですが、それは Quest 側の仕事です。)
// それには、以下のような感じにします。フィールドはあくまで一例です。
// bool IssueStayAliveConditions() => _targetNotable.IsActive;
public override bool IssueStayAliveConditions()
{
// 食糧がクエスト開始条件の3倍以上に増えたら false を返して消滅させます。
return IssueOwner.CurrentSettlement.ItemRoster.GetItemNumber(_requestedFood) < VillageFoodThreshold * 3;
}
// プレイヤーが Issue 解決を請け負えるかどうかの条件を設定できます。
protected override bool CanPlayerTakeQuestConditions(
Hero issueGiver, out PreconditionFlags flag, out Hero relationHero, out SkillObject skill)
{
// 各条件に対応する PreconditionFlags でビットマスクをかけていきます。
flag = PreconditionFlags.None;
relationHero = null;
skill = null;
// 友好度が低いと受けられないようにする。
if (issueGiver.GetRelationWithPlayer() < IssuePreConditionMinPlayerRelation)
{
// このフラグを立てたときには、誰と仲が悪くて受けられないのかを返す必要があるようです。
flag |= PreconditionFlags.Relation;
relationHero = issueGiver;
}
// 所属国同士が戦争中だと受けられないようにする。
if (issueGiver.MapFaction.IsAtWarWith(Hero.MainHero.MapFaction))
{
flag |= PreconditionFlags.AtWar;
}
// いずれかのフラグが立っていると false が返るので請け負えなくなります。
return flag == PreconditionFlags.None;
}
// 詳細不明。公式の Issue でもほぼ使われていません。
protected override void CompleteIssueWithTimedOutConsequences()
{
}
// Quest の発行処理です。
// コンストラクターに渡す日数は Quest の達成期限です。
protected override QuestBase GenerateIssueQuest(string questId)
{
return new VillageNeedsFoodIssueQuest
( questId, IssueOwner, RequestedFoodAmount, _requestedFood, RewardGold, CampaignTime.DaysFromNow(QuestTimeLimit));
}
protected override void OnGameLoad()
{
}
// Issue 発生の影響です。
protected override Dictionary<IssueEffect, float> GetIssueEffectsAndAmountInternal()
{
// 変化量は1日あたりのものです。Issue の自然消滅日数を考慮して設定しないと、
// トータルのペナルティが重くなりすぎてしまいます。
return new Dictionary
<IssueEffect,
float>() {
{ DefaultIssueEffects.VillageHearth, -0.5f },
{ DefaultIssueEffects.IssueOwnerPower, -0.2f }
};
}
public override bool DoTroopsSatisfyAlternativeSolution(TroopRoster troopRoster, out TextObject explanation)
{
explanation = TextObject.Empty;
return QuestHelper.CheckRosterForAlternativeSolution(troopRoster, GetTotalAlternativeSolutionNeededMenCount(), ref explanation, MinTroopTier);
}
// 任務に派遣可能な英雄の条件です。
// e1.6.2 にて条件による制限は撤廃され、代わりに担当者のスキルに応じて失敗確率が発生するようになりました。
/*
public override bool CompanionOrFamilyMemberClickableCondition(Hero companion, out TextObject explanation)
{
GetAlternativeSolutionRequiredCompanionSkills(out Dictionary<SkillObject, int> shouldHaveAll, out Dictionary<SkillObject, int> shouldHaveOneOfThem);
explanation = TextObject.Empty;
return QuestHelper.CheckCompanionForAlternativeSolution(companion.CharacterObject, ref explanation, shouldHaveAll, shouldHaveOneOfThem);
}
*/
public override bool IsTroopTypeNeededByAlternativeSolution(CharacterObject character)
{
return character.Tier >= MinTroopTier;
}
public override void AlternativeSolutionStartConsequence()
{
// 食糧の購入資金を差し引いています。
Hero.MainHero.ChangeHeroGold(-_requestedFood.Value * RequestedFoodAmount);
}
protected override void AlternativeSolutionEndWithSuccessConsequence()
{
// 担当キャラクターに対する報酬は、プロパティで設定してあるので勝手に適用されます。
// クリアしたことによる影響
RelationshipChangeWithIssueOwner = 10;
foreach (Hero hero in IssueOwner.CurrentSettlement.Notables)
{
// QuestGiver はすでに上げてあるので除外。
if (hero != IssueOwner)
{
ChangeRelationAction.ApplyPlayerRelation(hero, 2);
}
}
IssueOwner.AddPower(10f);
TraitLevelingHelper.OnIssueSolvedThroughQuest(
IssueOwner,
new Tuple
<TraitObject,
int>[] {
new Tuple
<TraitObject,
int>(DefaultTraits
.Honor,
50),
new Tuple
<TraitObject,
int>(DefaultTraits
.Mercy,
20) });
}
/*
private void GetAlternativeSolutionRequiredCompanionSkills(out Dictionary<SkillObject, int> shouldHaveAll, out Dictionary<SkillObject, int> shouldHaveOneOfThem)
{
// 必須スキル
// こちらの Dictionary に登録したスキルは、全てが指定レベル以上でなければなりません。
shouldHaveAll = new Dictionary<SkillObject, int>
{
{ DefaultSkills.Trade, CompanionSkillThreshold }
};
// 選択スキル
// こちらの Dictionary に登録したスキルは、いずれかが指定レベル以上でなければなりません。
shouldHaveOneOfThem = null;
// 例えば「近接武器スキルいずれか」という条件を追加したいのであれば、以下のように記述します。
// もちろん、しきい値は同一でなくても構いません。
//
// shouldHaveOneOfThem = new Dictionary<SkillObject, int>
// {
// { DefaultSkills.OneHanded, CompanionSkillThreshold },
// { DefaultSkills.TwoHanded, CompanionSkillThreshold },
// { DefaultSkills.Polearm, CompanionSkillThreshold }
// };
}
*/
}
// ----------------------------------------------------------------
// Issue の定義はここまで。
//
// ここからは Quest の定義です。
// ----------------------------------------------------------------
internal class VillageNeedsFoodIssueQuest : QuestBase
{
// これらのフィールドは、セーブ/ロードをまたいで保持されるべき数値なので、SaveableField 属性を与えています。
// SaveableCampaignBehaviorTypeDefiner の宣言だけではダメなようです。
[SaveableField(10)] private readonly ItemObject _foodToBeDelivered;
[SaveableField(20)] private readonly int _numFoodToBeDelivered;
[SaveableField(30)] private JournalLog _progressLog;
// Quest のタイトルです。
// ジャーナル (クエストログ) に表示されます。
// Issue のタイトルとは同じにも別にもできます。
public override TextObject Title
=> new TextObject
("村が食糧を求めている");
// ジャーナルで達成期限を非表示にするか否かです。
public override bool IsRemainingTimeHidden => false;
private TextObject QuestAcceptLog
{
get
{
TextObject textObject
= new TextObject
("{SETTLEMENT.LINK} の {QUEST_GIVER.LINK} に、{REQUESTED_FOOD} を持ってくるよう頼まれた。 報酬として {PAYMENT}{GOLD_ICON} を払ってくれるそうだ。"); StringHelpers.SetSettlementProperties("SETTLEMENT", QuestGiver.CurrentSettlement, textObject);
StringHelpers.SetCharacterProperties("QUEST_GIVER", QuestGiver.CharacterObject, textObject);
textObject.SetTextVariable("REQUESTED_FOOD", _foodToBeDelivered.Name)
.SetTextVariable("PAYMENT", RewardGold);
return textObject;
}
}
private TextObject QuestSuccessLog
{
get
{
TextObject textObject
= new TextObject
("{QUEST_GIVER.LINK} のもとに、約束していた食糧を届けた。"); StringHelpers.SetCharacterProperties("QUEST_GIVER", QuestGiver.CharacterObject, textObject);
return textObject;
}
}
private TextObject FoodGatheredInfo
{
get
{
TextObject textObject
= new TextObject
("必要な {FOOD} が集まりました。 依頼人の所に戻りましょう。"); textObject.SetTextVariable("FOOD", _foodToBeDelivered.Name);
return textObject;
}
}
private TextObject VillageRaidedLog
{
get
{
TextObject textObject
= new TextObject
("{VILLAGE.LINK} が略奪を受けたようだ。 もはや任務どころではない。"); StringHelpers.SetSettlementProperties("VILLAGE", QuestGiver.CurrentSettlement, textObject);
return textObject;
}
}
private TextObject WarDeclaredLog
{
get
{
TextObject textObject
= new TextObject
("{QUEST_GIVER.LINK} の国と戦争が始まった。 {?QUEST_GIVER.GENDER}彼女{?}彼{\\?}との約束もこれまでだろう。"); StringHelpers.SetCharacterProperties("QUEST_GIVER", QuestGiver.CharacterObject, textObject);
return textObject;
}
}
public VillageNeedsFoodIssueQuest(
string questId, Hero questGiver, int numFoodToBeDelivered, ItemObject foodToBeDelivered, int rewardGold, CampaignTime duration)
: base(questId, questGiver, duration, rewardGold)
{
_numFoodToBeDelivered = numFoodToBeDelivered;
_foodToBeDelivered = foodToBeDelivered;
SetDialogs();
InitializeQuestOnCreation();
}
private int CurrentFoodAmount => PartyBase.MainParty.ItemRoster.GetItemNumber(_foodToBeDelivered);
// クエスト進行中にゲームをセーブ -> ロードした場合の初期化はここで行われます。
protected override void InitializeQuestOnGameLoad()
{
SetDialogs();
}
// 会話の流れと、それに伴う処理のデリゲートを設定します。
protected override void SetDialogs()
{
OfferDialogFlow = DialogFlow.CreateDialogFlow(IssueManager.IssueClassicQuestStartToken)
.NpcLine("お手数ですが、よろしくお願いいたします。")
.Condition(() => Hero.OneToOneConversationHero == QuestGiver)
.Consequence(new ConversationSentence
.OnConsequenceDelegate(QuestAcceptedConsequences
)) .CloseDialog();
DiscussDialogFlow = DialogFlow.CreateDialogFlow(QuestManager.QuestDiscussToken)
.NpcLine("お頼みした仕事の首尾はいかがですかな?")
.Condition(() => Hero.OneToOneConversationHero == QuestGiver)
.BeginPlayerOptions()
.PlayerOption("うむ、ここに持ってまいった。")
.Condition(new ConversationSentence
.OnConditionDelegate(PlayerHasFood
)) .NpcLine("おお、ありがとうございます。 それでは、こちらをお納めくだされ。")
.Consequence(new ConversationSentence
.OnConsequenceDelegate(QuestFinishedConsequences
)) .PlayerOption("いま取り組んでいるところだ。 しばし待たれよ。")
.NpcLine("さようですか。 何卒よろしくお願いいたします。")
.EndPlayerOptions()
.CloseDialog();
// QuestCharacterDialogFlow = DialogFlow.CreateDialogFlow(QuestManager.CharacterTalkToken);
}
protected override void RegisterEvents()
{
CampaignEvents.PlayerInventoryExchangeEvent.AddNonSerializedListener(this, OnPlayerInventoryExchange);
CampaignEvents.RaidCompletedEvent.AddNonSerializedListener(this, OnRaidCompleted);
CampaignEvents.WarDeclared.AddNonSerializedListener(this, OnWarDeclared);
}
protected override void OnCompleteWithSuccess()
{
// プレイヤーキャラクターに対する報酬
// IssueBase.RewardGold が設定してあれば、QuestBase.RewardGold ↓ にも反映されています。
GiveGoldAction.ApplyBetweenCharacters(null, Hero.MainHero, RewardGold);
Hero.MainHero.AddSkillXp(DefaultSkills.Athletics, 100f);
// クリアしたことによる影響
RelationshipChangeWithQuestGiver = 10;
foreach (Hero hero in QuestGiver.CurrentSettlement.Notables)
{
// QuestGiver はすでに上げてあるので除外。
if (hero != QuestGiver)
{
ChangeRelationAction.ApplyPlayerRelation(hero, 2);
}
}
QuestGiver.AddPower(10f);
TraitLevelingHelper.OnIssueSolvedThroughQuest(
QuestGiver,
new Tuple
<TraitObject,
int>[] {
new Tuple
<TraitObject,
int>(DefaultTraits
.Honor,
50),
new Tuple
<TraitObject,
int>(DefaultTraits
.Mercy,
20) });
}
public override void OnFailed()
{
RelationshipChangeWithQuestGiver = -10;
QuestGiver.AddPower(-5f);
TraitLevelingHelper.OnIssueFailed(
QuestGiver,
new Tuple
<TraitObject,
int>[] {
new Tuple
<TraitObject,
int>(DefaultTraits
.Honor,
-50) });
}
protected override void OnTimedOut()
{
RelationshipChangeWithQuestGiver = -5;
QuestGiver.AddPower(-5f);
TraitLevelingHelper.OnIssueFailed(
QuestGiver,
new Tuple
<TraitObject,
int>[] {
new Tuple
<TraitObject,
int>(DefaultTraits
.Honor,
-25) });
}
// 以下、会話の特定の段階で実行される処理です。
// クエストを請けたとき
private void QuestAcceptedConsequences()
{
AddLog(QuestAcceptLog);
// プログレスバーを追加しています。
TextObject text
= new TextObject
("{FOOD}を{FOOD_NUMBER}集める。") .SetTextVariable("FOOD", _foodToBeDelivered.Name)
.SetTextVariable("FOOD_NUMBER", _numFoodToBeDelivered);
// text の部分を TextObject.Empty とすれば、純粋にプログレスバーだけの表示になります。
_progressLog
= AddDiscreteLog
(text,
new TextObject
("集めた食糧"),
0, _numFoodToBeDelivered
); UpdateProgress();
// これを呼ぶだけで、通知の表示や効果音再生など、クエスト開始に伴う処理が自動的に実行されます。
StartQuest();
}
// クエスト目標をクリアしたとき
private void QuestFinishedConsequences()
{
TransferFoodFromPlayerInventory();
AddLog(QuestSuccessLog);
// クエストがどういう結果に終わったのかを QuestBase に知らせる必要があります。
// CompleteQuestWithSuccess() なら成功
// CompleteQuestWithFail() なら失敗
// CompleteQuestWithCancel() なら中止
// CompleteQuestWithTimedOut() なら期限切れ
// CompleteQuestWithBetrayal() なら依頼人を裏切る形での決着
CompleteQuestWithSuccess();
}
private bool PlayerHasFood()
{
return CurrentFoodAmount >= _numFoodToBeDelivered;
}
private void TransferFoodFromPlayerInventory()
{
GiveItemAction.ApplyForParties(
PartyBase.MainParty, QuestGiver.CurrentSettlement.Party, _foodToBeDelivered, _numFoodToBeDelivered);
}
private void UpdateProgress()
{
_progressLog.UpdateCurrentProgress((int)MathF.Clamp(CurrentFoodAmount, 0f, _numFoodToBeDelivered));
}
// 以下、クエストの推移を判断するためのイベントリスナーです。
private void OnPlayerInventoryExchange(
List<ValueTuple<ItemRosterElement, int>> purchasedItems, List<ValueTuple<ItemRosterElement, int>> soldItems, bool isTrading)
{
UpdateProgress();
foreach ((ItemRosterElement purchasedItem, int _) in purchasedItems)
{
// インベントリーに Grain が入れられた場合
if (purchasedItem.EquipmentElement.Item == _foodToBeDelivered && PlayerHasFood())
{
// クリア条件を満たしたことを通知でプレイヤーに知らせています。
InformationManager.AddQuickInformation(FoodGatheredInfo);
break;
}
}
}
private void OnRaidCompleted(BattleSideEnum winnerSide, MapEvent mapEvent)
{
if (mapEvent.MapEventSettlement == QuestGiver.CurrentSettlement)
{
CompleteQuestWithCancel(VillageRaidedLog);
}
}
private void OnWarDeclared(IFaction faction1, IFaction faction2)
{
if ((faction1.MapFaction == Clan.PlayerClan.MapFaction && faction2.MapFaction == QuestGiver.MapFaction)
|| (faction2.MapFaction == Clan.PlayerClan.MapFaction && faction1.MapFaction == QuestGiver.MapFaction))
{
CompleteQuestWithCancel(WarDeclaredLog);
}
}
}
}
}