context: //--- Aura Script -----------------------------------------------------------
  // Walter's General Shop Part-Time Job
  //--- Description -----------------------------------------------------------
  // All quests used by the PTJ, and a script to handle the PTJ via hooks.
  //--- Notes -----------------------------------------------------------------
  // Definitions for the following base scripts have been improvised,
  // update with official script whenever possible:
  // * WalterExtDeliveryGlenisAeiraPtjBaseScript
  // * WalterExtDeliveryNerysStewartPtjBaseScript
  //---------------------------------------------------------------------------
  
  public class WalterPtjScript : GeneralScript
  {
  	const PtjType JobType = PtjType.GeneralShop;
  
  	const int Start = 7;
  	const int Report = 9;
  	const int Deadline = 19;
  	const int PerDay = 10;
  
  	int remaining = PerDay;
  
  	readonly int[] QuestIds = new int[]
  	{
  		508406, // Basic  Item delivery (Aeira)
  		508436, // Int    Item delivery (Aeira)
  		508466, // Adv    Item delivery (Aeira)
  		508407, // Basic  Item delivery (Manus)
  		508437, // Int    Item delivery (Manus)
  		508467, // Adv    Item delivery (Manus)
  		508438, // Int    Item delivery (Glenis -> Aeira)
  		508468, // Adv    Item delivery (Glenis -> Aeira)
  		508439, // Int    Item delivery (Nerys  -> Stewart)
  		508469, // Adv    Item delivery (Nerys  -> Stewart)
  
  		508503, // Basic  Weave 2 Braids
  		508504, // Basic  Weave 5 Cheap Leather Straps
  		508533, // Int    Weave 5 Common Leather Straps
  		508534, // Int    Weave 5 Fine Leather Straps
  		508535, // Int    Weave 3 Braids
  		508564, // Adv    Weave 5 Finest Leather Straps
  		508565, // Adv    Weave 5 Braids
  	};
  
  	public override void Load()
  	{
  		AddHook("_walter", "after_intro", AfterIntro);
  		AddHook("_walter", "before_keywords", BeforeKeywords);
  	}
  
  	public async Task<HookResult> AfterIntro(NpcScript npc, params object[] args)
  	{
  		// Call PTJ method after intro if it's time to report
  		if (npc.Player.IsDoingPtjFor(npc.NPC) && ErinnHour(Report, Deadline))
  		{
  			await AboutArbeit(npc);
  			return HookResult.Break;
  		}
  
  		return HookResult.Continue;
  	}
  
  	[On("ErinnMidnightTick")]
  	private void OnErinnMidnightTick(ErinnTime time)
  	{
  		// Reset available jobs
  		remaining = PerDay;
  	}
  
  	public async Task<HookResult> BeforeKeywords(NpcScript npc, params object[] args)
  	{
  		var keyword = args[0] as string;
  
  		// Hook PTJ keyword
  		if (keyword == "about_arbeit")
  		{
  			await AboutArbeit(npc);
  			await npc.Conversation();
  			npc.End();
  
  			return HookResult.End;
  		}
  
  		return HookResult.Continue;
  	}
  
  	public async Task AboutArbeit(NpcScript npc)
  	{
  		// Check if already doing another PTJ
  		if (npc.Player.IsDoingPtjNotFor(npc.NPC))
  		{
  			npc.Msg(L("Just finish what you're doing."));
  			return;
  		}
  
  		// Check if PTJ is in progress
  		if (npc.Player.IsDoingPtjFor(npc.NPC))
  		{
  			var result = npc.Player.GetPtjResult();
  
  			// Check if report time
  			if (!ErinnHour(Report, Deadline))
  			{
  				if (result == QuestResult.Perfect)
  				{
  					npc.Msg(L("It's not time yet."));
  				}
  				else
  				{
  					npc.Msg(L("Is everything alright?"));
  				}
  				return;
  			}
  
  			// Report?
  			npc.Msg(L("Have you finished your work?"), npc.Button(L("Report Now"), "@report"), npc.Button(L("Report Later"), "@later"));
  
  			if (await npc.Select() != "@report")
  			{
  				npc.Msg(L("Come when you're finished."));
  				return;
  			}
  
  			// Nothing done
  			if (result == QuestResult.None)
  			{
  				npc.Player.GiveUpPtj();
  
  				npc.Msg(npc.FavorExpression(), L("Don't ever come here again!"));
  				npc.ModifyRelation(0, -Random(3), 0);
  			}
  			// Low~Perfect result
  			else
  			{
  				npc.Msg(L("Phew... Well done.<br/>Take your pick."),
  					npc.Button(L("Report Later"), "@later"),
  					npc.PtjReport(result)
  					);
  				var reply = await npc.Select();
  
  				// Report later
  				if (!reply.StartsWith("@reward:"))
  				{
  					npc.Msg(npc.FavorExpression(), L("Hah..."));
  					return;
  				}
  
  				// Complete
  				npc.Player.CompletePtj(reply);
  				remaining--;
  
  				// Result msg
  				if (result == QuestResult.Perfect)
  				{
  					npc.Msg(npc.FavorExpression(), L("Good!"));
  					npc.ModifyRelation(0, Random(3), 0);
  				}
  				else if (result == QuestResult.Mid)
  				{
  					npc.Msg(npc.FavorExpression(), L("This is not enough..."));
  					npc.ModifyRelation(0, Random(1), 0);
  				}
  				else if (result == QuestResult.Low)
  				{
  					npc.Msg(npc.FavorExpression(), L("What is this!"));
  					npc.ModifyRelation(0, -Random(2), 0);
  				}
  			}
  			return;
  		}
  
  		// Check if PTJ time
  		if (!ErinnHour(Start, Deadline))
  		{
  			npc.Msg(L("It's not the right time."));
  			return;
  		}
  
  		// Check if not done today and if there are jobs remaining
  		if (!npc.Player.CanDoPtj(JobType, remaining))
  		{
  			npc.Msg(L("That's it for today."));
  			return;
  		}
  
  		// Offer PTJ
  		var randomPtj = GetRandomPtj(npc.Player, JobType, QuestIds);
  		var msg = "";
  
  		if (npc.Player.GetPtjDoneCount(JobType) == 0)
  			msg = L("Is this your first time? Good luck.");
  		else
  			msg = L("I'm counting on you as usual.");
  
  		var ptjTitle = "";
  		if ((randomPtj / 100) % 10 == 5) // Poll third least significant digit
  			ptjTitle = L("Looking for weavers.");
  		else
  			ptjTitle = L("Looking for help with delivery of goods in General Shop.");
  
  		npc.Msg(msg, npc.PtjDesc(randomPtj, L("Walter's General Shop Part-Time Job"), ptjTitle, PerDay, remaining, npc.Player.GetPtjDoneCount(JobType)));
  
  		if (await npc.Select() == "@accept")
  		{
  			if (npc.Player.GetPtjDoneCount(JobType) == 0)
  				npc.Msg(L("You have to finish your work before the deadline and come back to me.<br/>"));
  			else
  				npc.Msg(L("Well, then."));
  
  			npc.Player.StartPtj(randomPtj, npc.NPC.Name);
  		}
  		else
  		{
  			npc.Msg(L("I got it."));
  		}
  	}
  }
  
  public abstract class WalterDeliveryPtjBaseScript : QuestScript
  {
  	protected abstract int QuestId { get; }
  	protected abstract string LQuestDescription { get; }
  	protected abstract QuestLevel QuestLevel { get; }
  	protected abstract string LObjectiveDescription { get; }
  	protected abstract string LItemNotice { get; }
  	protected abstract int ItemId { get; }
  	protected abstract string NpcIdent { get; }
  
  	public override void Load()
  	{
  		SetId(QuestId);
  		SetName(L("General Shop Part-Time Job"));
  		SetDescription(LQuestDescription);
  
  		if (IsEnabled("QuestViewRenewal"))
  			SetCategory(QuestCategory.ById);
  
  		SetType(QuestType.Deliver);
  		SetPtjType(PtjType.GeneralShop);
  		SetLevel(QuestLevel);
  		SetHours(start: 7, report: 9, deadline: 19);
  
  		AddObjective("ptj", LObjectiveDescription, 0, 0, 0, Deliver(ItemId, NpcIdent));
  		AddHook(NpcIdent, "after_intro", AfterIntro);
  
  		AddRewards();
  	}
  
  	public async Task<HookResult> AfterIntro(NpcScript npc, params object[] args)
  	{
  		if (!npc.Player.QuestActive(this.Id, "ptj"))
  			return HookResult.Continue;
  
  		if (!npc.Player.HasItem(ItemId))
  			return HookResult.Continue;
  
  		npc.Player.RemoveItem(ItemId);
  		npc.Player.FinishQuestObjective(this.Id, "ptj");
  		npc.Player.Notice(LItemNotice);
  
  		await this.OnFinish(npc);
  
  		return HookResult.Break;
  	}
  
  	protected virtual async Task OnFinish(NpcScript npc)
  	{
  		await Task.Yield();
  	}
  
  	/// <remarks>
  	/// Rewards common among single-objective deliveries.
  	/// </remarks>
  	private void AddRewards()
  	{
  		switch (QuestLevel)
  		{
  			case QuestLevel.Basic:
  				AddReward(1, RewardGroupType.Gold, QuestResult.Perfect, Exp(250));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Perfect, Gold(150));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Mid, Exp(125));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Mid, Gold(75));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Low, Exp(50));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Low, Gold(30));
  
  				AddReward(2, RewardGroupType.Gold, QuestResult.Perfect, Exp(60));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Perfect, Gold(300));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Mid, Exp(30));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Mid, Gold(150));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Low, Exp(12));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Low, Gold(60));
  
  				AddReward(3, RewardGroupType.Exp, QuestResult.Perfect, Exp(370));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Perfect, Gold(60));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Mid, Exp(185));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Mid, Gold(30));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Low, Exp(74));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Low, Gold(12));
  
  				AddReward(4, RewardGroupType.Item, QuestResult.Perfect, Item(50004)); // Bread
  				AddReward(4, RewardGroupType.Item, QuestResult.Perfect, Gold(306));
  
  				AddReward(5, RewardGroupType.Item, QuestResult.Perfect, Item(40023)); // Gathering Knife
  				AddReward(5, RewardGroupType.Item, QuestResult.Perfect, Gold(312));
  
  				AddReward(6, RewardGroupType.Item, QuestResult.Perfect, Item(51001, 7)); // HP 10 Potion
  				AddReward(6, RewardGroupType.Item, QuestResult.Perfect, Gold(12));
  
  				AddReward(7, RewardGroupType.Item, QuestResult.Perfect, Item(51011, 5)); // Stamina 10 Potion
  				AddReward(7, RewardGroupType.Item, QuestResult.Perfect, Gold(312));
  				break;
  
  			case QuestLevel.Int:
  				AddReward(1, RewardGroupType.Gold, QuestResult.Perfect, Exp(320));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Perfect, Gold(220));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Mid, Exp(160));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Mid, Gold(110));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Low, Exp(64));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Low, Gold(44));
  
  				AddReward(2, RewardGroupType.Gold, QuestResult.Perfect, Exp(90));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Perfect, Gold(375));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Mid, Exp(45));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Mid, Gold(187));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Low, Exp(18));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Low, Gold(75));
  
  				AddReward(3, RewardGroupType.Exp, QuestResult.Perfect, Exp(480));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Perfect, Gold(80));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Mid, Exp(240));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Mid, Gold(40));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Low, Exp(96));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Low, Gold(16));
  
  				AddReward(4, RewardGroupType.Item, QuestResult.Perfect, Item(63020)); // Empty Bottle
  				AddReward(4, RewardGroupType.Item, QuestResult.Perfect, Gold(65));
  
  				AddReward(5, RewardGroupType.Item, QuestResult.Perfect, Item(50004)); // Bread
  				AddReward(5, RewardGroupType.Item, QuestResult.Perfect, Gold(306));
  
  				AddReward(6, RewardGroupType.Item, QuestResult.Perfect, Item(51001, 9)); // HP 10 Potion
  				AddReward(6, RewardGroupType.Item, QuestResult.Perfect, Gold(15));
  
  				AddReward(7, RewardGroupType.Item, QuestResult.Perfect, Item(51011, 5)); // Stamina 10 Potion
  				AddReward(7, RewardGroupType.Item, QuestResult.Perfect, Gold(415));
  				break;
  
  			case QuestLevel.Adv:
  				AddReward(1, RewardGroupType.Gold, QuestResult.Perfect, Exp(430));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Perfect, Gold(300));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Mid, Exp(215));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Mid, Gold(150));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Low, Exp(86));
  				AddReward(1, RewardGroupType.Gold, QuestResult.Low, Gold(60));
  
  				AddReward(2, RewardGroupType.Gold, QuestResult.Perfect, Exp(135));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Perfect, Gold(520));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Mid, Exp(87));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Mid, Gold(260));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Low, Exp(27));
  				AddReward(2, RewardGroupType.Gold, QuestResult.Low, Gold(104));
  
  				AddReward(3, RewardGroupType.Exp, QuestResult.Perfect, Exp(665));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Perfect, Gold(125));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Mid, Exp(332));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Mid, Gold(62));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Low, Exp(133));
  				AddReward(3, RewardGroupType.Exp, QuestResult.Low, Gold(25));
  
  				AddReward(4, RewardGroupType.Item, QuestResult.Perfect, Item(18024)); // Hairband
  				AddReward(4, RewardGroupType.Item, QuestResult.Perfect, Gold(48));
  
  				AddReward(5, RewardGroupType.Item, QuestResult.Perfect, Item(18012)); // Tork Merchant Cap
  				AddReward(5, RewardGroupType.Item, QuestResult.Perfect, Gold(48));
  
  				AddReward(6, RewardGroupType.Item, QuestResult.Perfect, Item(19001)); // Robe
  				AddReward(6, RewardGroupType.Item, QuestResult.Perfect, Gold(148));
  
  				AddReward(7, RewardGroupType.Item, QuestResult.Perfect, Item(19002)); // Slender Robe
  				AddReward(7, RewardGroupType.Item, QuestResult.Perfect, Gold(148));
  				break;
  
  			default:
  				Log.Warning("Walter PTJ: No reward set for quest ID {0}. Fell back to basic rewards.", QuestId);
  				goto case QuestLevel.Basic;
  		}
  	}
  }
  
  public abstract class WalterDeliveryAeiraPtjBaseScript : WalterDeliveryPtjBaseScript
  {
  	protected override string LQuestDescription { get { return L("I need to give this cubic puzzle to my daughter at the bookstore. Can you help me out? - Walter -"); } }
  	protected override string LObjectiveDescription { get { return L("Deliver the [Cubic Puzzle] to Aeira."); } }
  	protected override string LItemNotice { get { return L("You have given Cubic Puzzle to be Delivered to Aeira."); } }
  	protected override int ItemId { get { return 70006; } }
  
gt: 	protected override string NpcIdent { get { return "_aeira"; } }