Files
TheriapolisV3/Theriapolis.Core/Rules/Dialogue/ShopPricing.cs
T
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

81 lines
3.4 KiB
C#

using Theriapolis.Core.Rules.Reputation;
namespace Theriapolis.Core.Rules.Dialogue;
/// <summary>
/// Phase 6 M3 — disposition-driven shop modifiers per the plan §4.6.
///
/// NEMESIS → service refused
/// HOSTILE → service refused
/// ANTAGONISTIC..UNFRIENDLY → +25% prices, +10% sell discount
/// NEUTRAL → base prices
/// FAVORABLE → -10% buy
/// FRIENDLY → -20% buy
/// ALLIED → -30% buy
/// CHAMPION → -40% buy
///
/// Phase 6 M3 ships buy-side adjustment only; sell-side is mirrored at
/// 50% of the buy modifier so a friendly merchant pays a small premium
/// without dual-tunable knobs.
/// </summary>
public static class ShopPricing
{
/// <summary>True if the player can shop at all given the disposition score.</summary>
public static bool ServiceAvailable(int dispositionScore)
{
var label = DispositionLabels.For(dispositionScore);
return label != DispositionLabel.Nemesis && label != DispositionLabel.Hostile;
}
/// <summary>
/// Multiplier applied to a price the player pays. 1.0 = base; &gt;1 =
/// markup; &lt;1 = discount. Caller multiplies the item's listed cost
/// and rounds.
/// </summary>
public static float BuyMultiplier(int dispositionScore)
{
var label = DispositionLabels.For(dispositionScore);
return label switch
{
DispositionLabel.Nemesis => 99f, // shouldn't be called; sentinel
DispositionLabel.Hostile => 99f,
DispositionLabel.Antagonistic => 1.25f,
DispositionLabel.Unfriendly => 1.25f,
DispositionLabel.Neutral => 1.00f,
DispositionLabel.Favorable => 0.90f,
DispositionLabel.Friendly => 0.80f,
DispositionLabel.Allied => 0.70f,
DispositionLabel.Champion => 0.60f,
_ => 1.00f,
};
}
/// <summary>Multiplier applied to a price the merchant pays the player on sell-back.</summary>
public static float SellMultiplier(int dispositionScore)
{
// Mirror buy modifier toward 1.0 by 50%: friendly buy = 0.80 →
// friendly sell = 0.60 (you still take a haircut), antagonistic
// buy = 1.25 → antagonistic sell = 0.30.
var label = DispositionLabels.For(dispositionScore);
return label switch
{
DispositionLabel.Antagonistic => 0.35f,
DispositionLabel.Unfriendly => 0.40f,
DispositionLabel.Neutral => 0.50f,
DispositionLabel.Favorable => 0.55f,
DispositionLabel.Friendly => 0.60f,
DispositionLabel.Allied => 0.65f,
DispositionLabel.Champion => 0.70f,
_ => 0f, // refused at Hostile / Nemesis
};
}
/// <summary>Buy price for one unit (rounded up). Cost is in the item's "cost_fang" or equivalent unit.</summary>
public static int BuyPriceFor(int baseCost, int dispositionScore)
=> System.Math.Max(1, (int)System.Math.Ceiling(baseCost * BuyMultiplier(dispositionScore)));
/// <summary>Sell price for one unit (rounded down).</summary>
public static int SellPriceFor(int baseCost, int dispositionScore)
=> System.Math.Max(0, (int)System.Math.Floor(baseCost * SellMultiplier(dispositionScore)));
}