Soul layer: imprints, warmth echoes, and consequences
Ports the Unity POC soul systems into the sim: - Warmth echoes: villagers remember who fed them (+0.15) and who turned them away (-0.25). Ask-for-food targeting runs on memory, not omniscience; the remembered-cold are not asked. Echoes fade daily. - Refusing the hungry while holding food marks the refuser (others-regard debris) and is logged; empty-handed is not refusal. - Harvesting below the 50% commons line marks the taker per unit taken. - Soul-to-soul atmospheric pressure (SoulFieldSystem port): encased souls darken nearby atmospheres, clear souls lighten them. - Debris costs biologically: nourishment drains faster and meals restore less as the soul encases. - Instrumentation: avg_debris + refusals in daily stats; per-villager nourish/health/debris; refusals in the contact log; map dots dim with encasement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,38 @@ public partial class GameManager : Node2D
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Soul-to-soul atmospheric pressure (ported from the Unity POC's
|
||||||
|
/// SoulFieldSystem): souls in proximity press on each other's
|
||||||
|
/// atmospheres. An encased soul darkens the room; a clear soul makes
|
||||||
|
/// it slightly easier for everyone near them to radiate. Both
|
||||||
|
/// directions apply; the net effect depends on relative debris.
|
||||||
|
/// </summary>
|
||||||
|
private void ApplySoulPressure()
|
||||||
|
{
|
||||||
|
const float radius = 8f;
|
||||||
|
const float strength = 0.00004f;
|
||||||
|
|
||||||
|
for (int i = 0; i < Npcs.Count; i++)
|
||||||
|
{
|
||||||
|
for (int j = i + 1; j < Npcs.Count; j++)
|
||||||
|
{
|
||||||
|
var a = Npcs[i];
|
||||||
|
var b = Npcs[j];
|
||||||
|
float dist = a.Position.DistanceTo(b.Position);
|
||||||
|
if (dist > radius) continue;
|
||||||
|
|
||||||
|
float falloff = 1f - dist / radius;
|
||||||
|
|
||||||
|
float fromA = Mathf.Lerp(-strength, strength, a.Imprint.Total) * falloff;
|
||||||
|
b.Imprint.ExternalPerception = Mathf.Clamp(b.Imprint.ExternalPerception + fromA, 0f, 1f);
|
||||||
|
|
||||||
|
float fromB = Mathf.Lerp(-strength, strength, b.Imprint.Total) * falloff;
|
||||||
|
a.Imprint.ExternalPerception = Mathf.Clamp(a.Imprint.ExternalPerception + fromB, 0f, 1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Where the community's feet actually are — new communal
|
/// <summary>Where the community's feet actually are — new communal
|
||||||
/// buildings are founded here, not at a scripted town center.</summary>
|
/// buildings are founded here, not at a scripted town center.</summary>
|
||||||
public Vector2 CommunityCentroid()
|
public Vector2 CommunityCentroid()
|
||||||
@@ -113,11 +145,15 @@ public partial class GameManager : Node2D
|
|||||||
foreach (var npc in Npcs)
|
foreach (var npc in Npcs)
|
||||||
npc.Tick(World, this);
|
npc.Tick(World, this);
|
||||||
|
|
||||||
|
ApplySoulPressure();
|
||||||
|
|
||||||
if (TotalTicks % 1440 == 0)
|
if (TotalTicks % 1440 == 0)
|
||||||
{
|
{
|
||||||
World.RegenerateDaily(SeasonFoodMult, SeasonWoodMult);
|
World.RegenerateDaily(SeasonFoodMult, SeasonWoodMult);
|
||||||
foreach (var building in Buildings)
|
foreach (var building in Buildings)
|
||||||
building.DailyUpkeep();
|
building.DailyUpkeep();
|
||||||
|
foreach (var npc in Npcs)
|
||||||
|
npc.FadeEchoes(0.005f);
|
||||||
StatsLogger.LogDay(this);
|
StatsLogger.LogDay(this);
|
||||||
if (Day % 10 == 0)
|
if (Day % 10 == 0)
|
||||||
GD.Print($"[WorldSim] Day {Day} complete.");
|
GD.Print($"[WorldSim] Day {Day} complete.");
|
||||||
|
|||||||
+36
-11
@@ -52,6 +52,26 @@ public class Npc
|
|||||||
/// souls honor the 50% commons floor; selfish souls strip to ~15%.</summary>
|
/// souls honor the 50% commons floor; selfish souls strip to ~15%.</summary>
|
||||||
public float HarvestSeekFloor => Mathf.Max(0.05f, Soul.Sustainability * 0.5f) + 0.05f;
|
public float HarvestSeekFloor => Mathf.Max(0.05f, Soul.Sustainability * 0.5f) + 0.05f;
|
||||||
|
|
||||||
|
// --- Social memory: warmth echoes (Unity POC port) --------------------
|
||||||
|
// Not knowledge — feeling. A villager doesn't know who is generous;
|
||||||
|
// they remember who fed them and who turned them away.
|
||||||
|
|
||||||
|
private readonly Dictionary<string, float> _echoes = new();
|
||||||
|
|
||||||
|
public float GetWarmth(string name) => _echoes.TryGetValue(name, out float w) ? w : 0f;
|
||||||
|
|
||||||
|
public void RecordEcho(string name, float delta) =>
|
||||||
|
_echoes[name] = Mathf.Clamp(GetWarmth(name) + delta, -1f, 1f);
|
||||||
|
|
||||||
|
/// <summary>Echoes drift back toward neutral — called once per day.
|
||||||
|
/// A single contact fades in weeks; a pattern of contact holds.</summary>
|
||||||
|
public void FadeEchoes(float amount)
|
||||||
|
{
|
||||||
|
var keys = new List<string>(_echoes.Keys);
|
||||||
|
foreach (var key in keys)
|
||||||
|
_echoes[key] = Mathf.MoveToward(_echoes[key], 0f, amount);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Ticks spent in each state since the last daily snapshot —
|
/// <summary>Ticks spent in each state since the last daily snapshot —
|
||||||
/// the raw material of emergent roles: what a person mostly does is
|
/// the raw material of emergent roles: what a person mostly does is
|
||||||
/// who they are becoming. Indexed by (int)NpcState.</summary>
|
/// who they are becoming. Indexed by (int)NpcState.</summary>
|
||||||
@@ -107,13 +127,16 @@ public class Npc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Metabolism: nourishment drains slowly; eat from the pack when hungry.
|
// Metabolism: nourishment drains slowly; eat from the pack when hungry.
|
||||||
// This is the per-capita demand that keeps the economy from saturating.
|
// The soul layer bites here (Unity POC rule): a burdened soul wears
|
||||||
Nourishment -= 0.35f / 1440f;
|
// faster, and the same meal gives it less. Debris is not a scoreboard —
|
||||||
|
// it is a way of being that costs.
|
||||||
|
float debris = Imprint.Total;
|
||||||
|
Nourishment -= (0.35f / 1440f) * (1f + debris);
|
||||||
if (Nourishment < 0.7f &&
|
if (Nourishment < 0.7f &&
|
||||||
Inventory.TryGetValue(MaterialKind.Food, out float food) && food >= 1f)
|
Inventory.TryGetValue(MaterialKind.Food, out float food) && food >= 1f)
|
||||||
{
|
{
|
||||||
Inventory[MaterialKind.Food] = food - 1f;
|
Inventory[MaterialKind.Food] = food - 1f;
|
||||||
Nourishment = Mathf.Min(1f, Nourishment + 0.25f);
|
Nourishment = Mathf.Min(1f, Nourishment + 0.25f * (1f - debris * 0.5f));
|
||||||
}
|
}
|
||||||
if (Nourishment <= 0.05f) Health = Mathf.Max(0f, Health - 0.01f);
|
if (Nourishment <= 0.05f) Health = Mathf.Max(0f, Health - 0.01f);
|
||||||
Nourishment = Mathf.Clamp(Nourishment, 0f, 1f);
|
Nourishment = Mathf.Clamp(Nourishment, 0f, 1f);
|
||||||
@@ -244,8 +267,7 @@ public class Npc
|
|||||||
MoveToward(_giftSource.Position);
|
MoveToward(_giftSource.Position);
|
||||||
if (Position.DistanceTo(_giftSource.Position) < ArriveDist)
|
if (Position.DistanceTo(_giftSource.Position) < ArriveDist)
|
||||||
{
|
{
|
||||||
if (TradeSystem.CanTrade(_giftSource, this, out var mat))
|
TradeSystem.RequestGift(_giftSource, this);
|
||||||
TradeSystem.Execute(_giftSource, this, mat);
|
|
||||||
_giftSource = null;
|
_giftSource = null;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -280,15 +302,18 @@ public class Npc
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float bestDist = float.MaxValue;
|
// Ask whoever might help — chosen by memory, not omniscience.
|
||||||
|
// The warm are asked first; the remembered-cold aren't asked
|
||||||
|
// at all. A stranger gets the benefit of the doubt once.
|
||||||
|
float bestScore = float.MinValue;
|
||||||
foreach (var other in gm.Npcs)
|
foreach (var other in gm.Npcs)
|
||||||
{
|
{
|
||||||
if (other == this) continue;
|
if (other == this) continue;
|
||||||
// Everyone knows better than to ask the ungenerous.
|
float warmth = GetWarmth(other.Name);
|
||||||
if (other.Soul.Generosity < 0.3f) continue;
|
if (warmth < -0.3f) continue;
|
||||||
if (!other.Inventory.TryGetValue(MaterialKind.Food, out float f) || f <= 3f) continue;
|
if (!other.Inventory.TryGetValue(MaterialKind.Food, out float f) || f <= 3f) continue;
|
||||||
float d = Position.DistanceSquaredTo(other.Position);
|
float score = warmth * 40f - Position.DistanceTo(other.Position);
|
||||||
if (d < bestDist) { bestDist = d; _giftSource = other; }
|
if (score > bestScore) { bestScore = score; _giftSource = other; }
|
||||||
}
|
}
|
||||||
if (_giftSource != null) return;
|
if (_giftSource != null) return;
|
||||||
}
|
}
|
||||||
@@ -308,7 +333,7 @@ public class Npc
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Activity = $"harvesting {_targetNode.Kind.ToString().ToLower()}";
|
Activity = $"harvesting {_targetNode.Kind.ToString().ToLower()}";
|
||||||
float taken = world.Harvest(_targetNode, requested: 2f, Soul);
|
float taken = world.Harvest(_targetNode, requested: 2f, this);
|
||||||
if (taken <= 0f) { _targetNode = null; return; } // node at its floor — re-seek
|
if (taken <= 0f) { _targetNode = null; return; } // node at its floor — re-seek
|
||||||
Inventory.TryGetValue(_targetNode.Kind, out float have);
|
Inventory.TryGetValue(_targetNode.Kind, out float have);
|
||||||
Inventory[_targetNode.Kind] = have + taken;
|
Inventory[_targetNode.Kind] = have + taken;
|
||||||
|
|||||||
+24
-4
@@ -20,10 +20,12 @@ public static class StatsLogger
|
|||||||
"harvested,trades," +
|
"harvested,trades," +
|
||||||
"avg_fatigue,avg_nourish,avg_health,carried," +
|
"avg_fatigue,avg_nourish,avg_health,carried," +
|
||||||
"gather_ticks,build_ticks,trade_ticks,rest_ticks,social_ticks," +
|
"gather_ticks,build_ticks,trade_ticks,rest_ticks,social_ticks," +
|
||||||
"shelters_done,ruins,avg_cond,upkeep_stock,burn_per_day,modules,granary_food";
|
"shelters_done,ruins,avg_cond,upkeep_stock,burn_per_day,modules,granary_food," +
|
||||||
|
"avg_debris,refusals";
|
||||||
|
|
||||||
private const string NpcHeader =
|
private const string NpcHeader =
|
||||||
"day,npc,name,soul,dominant,gather_ticks,build_ticks,trade_ticks,rest_ticks,social_ticks";
|
"day,npc,name,soul,dominant,gather_ticks,build_ticks,trade_ticks,rest_ticks,social_ticks," +
|
||||||
|
"nourish,health,debris";
|
||||||
|
|
||||||
private const string GiftHeader =
|
private const string GiftHeader =
|
||||||
"day,giver,giver_soul,receiver,receiver_soul,material,amount";
|
"day,giver,giver_soul,receiver,receiver_soul,material,amount";
|
||||||
@@ -52,6 +54,15 @@ public static class StatsLogger
|
|||||||
$"{material},{amount.ToString("0.0", CultureInfo.InvariantCulture)}\n");
|
$"{material},{amount.ToString("0.0", CultureInfo.InvariantCulture)}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Every refusal too — the gift log is really a contact log.</summary>
|
||||||
|
public static void LogRefusal(Npc giver, Npc asker)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_giftPath)) return;
|
||||||
|
int day = GameManager.Instance?.Day ?? -1;
|
||||||
|
File.AppendAllText(_giftPath,
|
||||||
|
$"{day},{giver.Name},{giver.Soul.Type},{asker.Name},{asker.Soul.Type},Refusal,0.0\n");
|
||||||
|
}
|
||||||
|
|
||||||
public static void LogDay(GameManager gm)
|
public static void LogDay(GameManager gm)
|
||||||
{
|
{
|
||||||
var ci = CultureInfo.InvariantCulture;
|
var ci = CultureInfo.InvariantCulture;
|
||||||
@@ -123,7 +134,12 @@ public static class StatsLogger
|
|||||||
row.Append(stock.ToString("0.0", ci)).Append(',');
|
row.Append(stock.ToString("0.0", ci)).Append(',');
|
||||||
row.Append(burn.ToString("0.0", ci)).Append(',');
|
row.Append(burn.ToString("0.0", ci)).Append(',');
|
||||||
row.Append(modules).Append(',');
|
row.Append(modules).Append(',');
|
||||||
row.Append(granaryFood.ToString("0.0", ci));
|
row.Append(granaryFood.ToString("0.0", ci)).Append(',');
|
||||||
|
|
||||||
|
float debrisSum = 0f;
|
||||||
|
foreach (var npc in gm.Npcs) debrisSum += npc.Imprint.Total;
|
||||||
|
row.Append((debrisSum / count).ToString("0.0000", ci)).Append(',');
|
||||||
|
row.Append(TradeSystem.RefusalsToday);
|
||||||
|
|
||||||
File.AppendAllText(_path, row + "\n");
|
File.AppendAllText(_path, row + "\n");
|
||||||
|
|
||||||
@@ -143,7 +159,10 @@ public static class StatsLogger
|
|||||||
.Append(t[(int)NpcState.Building]).Append(',')
|
.Append(t[(int)NpcState.Building]).Append(',')
|
||||||
.Append(t[(int)NpcState.Trading]).Append(',')
|
.Append(t[(int)NpcState.Trading]).Append(',')
|
||||||
.Append(t[(int)NpcState.Resting]).Append(',')
|
.Append(t[(int)NpcState.Resting]).Append(',')
|
||||||
.Append(t[(int)NpcState.Socializing]).Append('\n');
|
.Append(t[(int)NpcState.Socializing]).Append(',')
|
||||||
|
.Append(npc.Nourishment.ToString("0.000", ci)).Append(',')
|
||||||
|
.Append(npc.Health.ToString("0.0", ci)).Append(',')
|
||||||
|
.Append(npc.Imprint.Total.ToString("0.0000", ci)).Append('\n');
|
||||||
System.Array.Clear(npc.StateTicksToday, 0, npc.StateTicksToday.Length);
|
System.Array.Clear(npc.StateTicksToday, 0, npc.StateTicksToday.Length);
|
||||||
}
|
}
|
||||||
File.AppendAllText(_npcPath, npcRows.ToString());
|
File.AppendAllText(_npcPath, npcRows.ToString());
|
||||||
@@ -151,5 +170,6 @@ public static class StatsLogger
|
|||||||
// Reset per-day counters
|
// Reset per-day counters
|
||||||
gm.World.HarvestedToday = 0f;
|
gm.World.HarvestedToday = 0f;
|
||||||
TradeSystem.TradesToday = 0;
|
TradeSystem.TradesToday = 0;
|
||||||
|
TradeSystem.RefusalsToday = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,42 @@ public static class TradeSystem
|
|||||||
/// <summary>Trades since the last daily stats snapshot.</summary>
|
/// <summary>Trades since the last daily stats snapshot.</summary>
|
||||||
public static int TradesToday;
|
public static int TradesToday;
|
||||||
|
|
||||||
|
/// <summary>Refusals since the last daily stats snapshot.</summary>
|
||||||
|
public static int RefusalsToday;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A hungry villager asks another for food, face to face.
|
||||||
|
///
|
||||||
|
/// Willingness comes from the soul (or from a warm history with this
|
||||||
|
/// particular person). Refusing the hungry while holding food is a
|
||||||
|
/// deliberate darkening of another — it marks the refuser's soul
|
||||||
|
/// (others-regard debris, per DESIGN.md) and leaves a cold echo in the
|
||||||
|
/// asker's memory. The world keeps score the only way it knows how.
|
||||||
|
/// </summary>
|
||||||
|
public static void RequestGift(Npc giver, Npc asker)
|
||||||
|
{
|
||||||
|
giver.Inventory.TryGetValue(MaterialKind.Food, out float giverFood);
|
||||||
|
bool hasFood = giverFood > 3f;
|
||||||
|
bool willing = giver.Soul.Generosity >= 0.3f || giver.GetWarmth(asker.Name) > 0.5f;
|
||||||
|
|
||||||
|
if (hasFood && willing)
|
||||||
|
{
|
||||||
|
Execute(giver, asker, MaterialKind.Food);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFood && !willing)
|
||||||
|
{
|
||||||
|
// Refused with a full pack — the act that costs.
|
||||||
|
RefusalsToday++;
|
||||||
|
asker.RecordEcho(giver.Name, -0.25f);
|
||||||
|
giver.Imprint.OthersRegard = Godot.Mathf.Clamp(giver.Imprint.OthersRegard + 0.02f, 0f, 1f);
|
||||||
|
StatsLogger.LogRefusal(giver, asker);
|
||||||
|
}
|
||||||
|
// Empty-handed is not refusal: no debris, no echo. You cannot darken
|
||||||
|
// your soul with what you do not have.
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Need-based: the trigger is a person in need, not an inventory gap.
|
/// Need-based: the trigger is a person in need, not an inventory gap.
|
||||||
/// Food flows to the hungry first; raw material surplus vs. genuine
|
/// Food flows to the hungry first; raw material surplus vs. genuine
|
||||||
@@ -52,6 +88,10 @@ public static class TradeSystem
|
|||||||
|
|
||||||
StatsLogger.LogGift(giver, receiver, material, given);
|
StatsLogger.LogGift(giver, receiver, material, given);
|
||||||
|
|
||||||
|
// Warmth lands on both sides — more on the one who was fed.
|
||||||
|
receiver.RecordEcho(giver.Name, +0.15f);
|
||||||
|
giver.RecordEcho(receiver.Name, +0.05f);
|
||||||
|
|
||||||
// The kindness lands on both atmospheres — gently.
|
// The kindness lands on both atmospheres — gently.
|
||||||
giver.Imprint.ExternalPerception -= 0.002f;
|
giver.Imprint.ExternalPerception -= 0.002f;
|
||||||
receiver.Imprint.ExternalPerception -= 0.002f;
|
receiver.Imprint.ExternalPerception -= 0.002f;
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ public partial class Visualization : Node2D
|
|||||||
// Selfish souls get a dark red ring — same dot, different halo.
|
// Selfish souls get a dark red ring — same dot, different halo.
|
||||||
if (npc.Soul.Type == SoulType.Selfish)
|
if (npc.Soul.Type == SoulType.Selfish)
|
||||||
DrawCircle(p, 5.5f, new Color(0.75f, 0.15f, 0.15f));
|
DrawCircle(p, 5.5f, new Color(0.75f, 0.15f, 0.15f));
|
||||||
DrawCircle(p, 3.5f, StateColor(npc.State));
|
// Debris dims the dot — encasement made visible.
|
||||||
|
DrawCircle(p, 3.5f, StateColor(npc.State).Darkened(npc.Imprint.Total * 0.7f));
|
||||||
DrawString(font, p + new Vector2(5f, -4f), $"{i + 1}",
|
DrawString(font, p + new Vector2(5f, -4f), $"{i + 1}",
|
||||||
fontSize: 11, modulate: TextMain);
|
fontSize: 11, modulate: TextMain);
|
||||||
}
|
}
|
||||||
@@ -121,13 +122,14 @@ public partial class Visualization : Node2D
|
|||||||
|
|
||||||
if (npc.Soul.Type == SoulType.Selfish)
|
if (npc.Soul.Type == SoulType.Selfish)
|
||||||
DrawCircle(new Vector2(PanelX + 24, y - 5f), 6f, new Color(0.75f, 0.15f, 0.15f));
|
DrawCircle(new Vector2(PanelX + 24, y - 5f), 6f, new Color(0.75f, 0.15f, 0.15f));
|
||||||
DrawCircle(new Vector2(PanelX + 24, y - 5f), 4.5f, StateColor(npc.State));
|
DrawCircle(new Vector2(PanelX + 24, y - 5f), 4.5f,
|
||||||
|
StateColor(npc.State).Darkened(npc.Imprint.Total * 0.7f));
|
||||||
DrawString(font, new Vector2(PanelX + 36, y),
|
DrawString(font, new Vector2(PanelX + 36, y),
|
||||||
$"{i + 1,2} {npc.State,-11} {npc.Activity}",
|
$"{i + 1,2} {npc.State,-11} {npc.Activity}",
|
||||||
fontSize: 13, modulate: TextMain);
|
fontSize: 13, modulate: TextMain);
|
||||||
DrawString(font, new Vector2(PanelX + 420, y),
|
DrawString(font, new Vector2(PanelX + 400, y),
|
||||||
$"F {npc.Fatigue,3:0} N {npc.Nourishment * 100,3:0} C {npc.TotalCarried(),4:0.0}" +
|
$"F {npc.Fatigue,3:0} N {npc.Nourishment * 100,3:0} D {npc.Imprint.Total * 100,2:0}" +
|
||||||
$" mostly {npc.DominantStateToday().ToString().ToLower()[..4]}",
|
$" C {npc.TotalCarried(),4:0.0} {npc.DominantStateToday().ToString().ToLower()[..4]}",
|
||||||
fontSize: 12, modulate: TextDim);
|
fontSize: 12, modulate: TextDim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-2
@@ -74,17 +74,28 @@ public class World
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Harvest respecting the harvester's sustainability weight:
|
/// Harvest respecting the harvester's sustainability weight:
|
||||||
/// a South soul stops at ~50% of the node (SPEC §10) so nodes stay healthy.
|
/// a South soul stops at ~50% of the node (SPEC §10) so nodes stay healthy.
|
||||||
|
/// Taking from below the commons line marks the taker's soul — it is
|
||||||
|
/// taking from everyone, whether anyone sees it or not.
|
||||||
/// Returns the amount actually taken.
|
/// Returns the amount actually taken.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float Harvest(ResourceNode node, float requested, SoulProfile soul)
|
public float Harvest(ResourceNode node, float requested, Npc npc)
|
||||||
{
|
{
|
||||||
if (node.Kind == MaterialKind.Water) return requested; // unlimited at source
|
if (node.Kind == MaterialKind.Water) return requested; // unlimited at source
|
||||||
|
|
||||||
float floorFraction = soul.Sustainability * 0.5f; // how much of the node they leave standing
|
float floorFraction = npc.Soul.Sustainability * 0.5f; // how much of the node they leave standing
|
||||||
float takeable = Math.Max(0f, node.Amount - node.MaxAmount * floorFraction);
|
float takeable = Math.Max(0f, node.Amount - node.MaxAmount * floorFraction);
|
||||||
float taken = Math.Min(requested, takeable);
|
float taken = Math.Min(requested, takeable);
|
||||||
node.Amount -= taken;
|
node.Amount -= taken;
|
||||||
HarvestedToday += taken;
|
HarvestedToday += taken;
|
||||||
|
|
||||||
|
float commons = node.MaxAmount * 0.5f;
|
||||||
|
if (node.Amount < commons)
|
||||||
|
{
|
||||||
|
float takenBelow = Math.Min(taken, commons - node.Amount);
|
||||||
|
npc.Imprint.OthersRegard =
|
||||||
|
Math.Clamp(npc.Imprint.OthersRegard + takenBelow * 0.004f, 0f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
return taken;
|
return taken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user