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>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
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; >1 =
|
||||
/// markup; <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)));
|
||||
}
|
||||
Reference in New Issue
Block a user