162 lines
7.1 KiB
C#
162 lines
7.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
// Правила добычи -- только числа (заполняются из EconomyConfig в App/GameModules).
|
|
public sealed class ProductionRules
|
|
{
|
|
// Норма T1 в час на одного рабочего, по профессиям (порядок как в WorkJob)
|
|
public double[] PerWorkerPerHour = { 2.0, 2.0, 1.5, 1.0, 0.4 };
|
|
|
|
public int SlotsPerBuilding = 30; // мест в здании 1 уровня (Лесопилка T1 -- 30)
|
|
public int SlotsPerLevel = 10; // + мест за каждый уровень выше первого. Не утверждено
|
|
public double EfficiencyPerLevel = 0.1; // + к норме за каждый уровень выше первого (×1.1, ×1.2...). Не утверждено
|
|
}
|
|
|
|
// МОДУЛЬ ЭКОНОМИКИ: добыча через рабочих (этап 3). Документ: Docs/Modules/Economy.md
|
|
// Без Unity: правила те же, что будут на сервере.
|
|
//
|
|
// Ресурс производят люди, здание даёт только места и эффективность:
|
|
// добыча в час = min(рабочие, места) × норма × эффективность уровня зданий.
|
|
// Какие здания стоят в городе, сообщает слой приложения (App/CitySync) -- модуль их не ищет.
|
|
// Места запоминаются в сохранении: на карте мира и офлайн город не открыт, а добыча идёт.
|
|
//
|
|
// Produce вызывает модуль Населения в своём расчёте, поэтому в игре, офлайн и при перемотке правила одни.
|
|
// На склад идут только целые единицы, доли копятся здесь (склад хранит float).
|
|
public sealed class ProductionService : IProduction, ISaveParticipant
|
|
{
|
|
const double Epsilon = 1e-9;
|
|
|
|
readonly ProductionRules rules;
|
|
readonly int[] slots = new int[WorkJobs.Count];
|
|
readonly double[] efficiency = new double[WorkJobs.Count];
|
|
readonly double[] pending = new double[WorkJobs.Count]; // добыто, но ещё не отдано на склад (< 1)
|
|
|
|
public ProductionService(ProductionRules rules)
|
|
{
|
|
this.rules = rules ?? new ProductionRules();
|
|
ResetState();
|
|
}
|
|
|
|
public ProductionRules Rules => rules;
|
|
|
|
// ---------------- Здания города ----------------
|
|
|
|
// Здания-рабочие места: (профессия, уровень здания). Вызывает App/CitySync, когда город изменился.
|
|
public void SetBuildings(IEnumerable<KeyValuePair<WorkJob, int>> buildings)
|
|
{
|
|
var weighted = new double[WorkJobs.Count];
|
|
Array.Clear(slots, 0, slots.Length);
|
|
foreach (var b in buildings)
|
|
{
|
|
int j = (int)b.Key;
|
|
if (j < 0 || j >= WorkJobs.Count) continue;
|
|
int level = Math.Max(1, b.Value);
|
|
int s = BuildingSlots(level);
|
|
slots[j] += s;
|
|
weighted[j] += s * BuildingEfficiency(level);
|
|
}
|
|
for (int j = 0; j < WorkJobs.Count; j++)
|
|
efficiency[j] = slots[j] > 0 ? weighted[j] / slots[j] : 1.0;
|
|
}
|
|
|
|
public int BuildingSlots(int level) => rules.SlotsPerBuilding + rules.SlotsPerLevel * (Math.Max(1, level) - 1);
|
|
public double BuildingEfficiency(int level) => 1.0 + rules.EfficiencyPerLevel * (Math.Max(1, level) - 1);
|
|
|
|
// ---------------- Контракт IProduction ----------------
|
|
|
|
public int Slots(WorkJob job) => slots[(int)job];
|
|
public float Efficiency(WorkJob job) => (float)efficiency[(int)job];
|
|
|
|
public float RatePerHour(ResourceType type)
|
|
{
|
|
if (!WorkJobs.TryFromResource(type, out WorkJob job)) return 0f;
|
|
return (float)JobRate(job);
|
|
}
|
|
|
|
double JobRate(WorkJob job)
|
|
{
|
|
int j = (int)job;
|
|
if (!GameServices.TryGet(out IPopulation population)) return 0;
|
|
int working = Math.Min(population.GetWorkers(job), slots[j]);
|
|
return working * rules.PerWorkerPerHour[j] * efficiency[j];
|
|
}
|
|
|
|
public void Produce(double hours)
|
|
{
|
|
if (hours <= 0 || !GameServices.TryGet(out IEconomy economy)) return;
|
|
for (int j = 0; j < WorkJobs.Count; j++)
|
|
{
|
|
var job = (WorkJob)j;
|
|
pending[j] += JobRate(job) * hours;
|
|
int whole = (int)Math.Floor(pending[j] + Epsilon);
|
|
if (whole <= 0) continue;
|
|
pending[j] = Math.Max(0, pending[j] - whole);
|
|
economy.Add(WorkJobs.Resource(job), whole); // сверх лимита склада Экономика не прибавит
|
|
}
|
|
}
|
|
|
|
// ---------------- Сохранение (секция "production") ----------------
|
|
// slots=<места по профессиям> eff=<эффективность> pending=<доли>
|
|
|
|
public string SaveKey => "production";
|
|
|
|
public string Capture()
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.Append("slots=").Append(Join(slots)).Append('\n');
|
|
sb.Append("eff=").Append(Join(efficiency)).Append('\n');
|
|
sb.Append("pending=").Append(Join(pending)).Append('\n');
|
|
return sb.ToString();
|
|
}
|
|
|
|
public void Restore(string data)
|
|
{
|
|
ResetState();
|
|
if (string.IsNullOrEmpty(data)) return;
|
|
foreach (var raw in data.Split('\n'))
|
|
{
|
|
string line = raw.Trim();
|
|
if (line.StartsWith("slots=")) ParseInto(line.Substring(6), v => Math.Max(0, (int)v), slots);
|
|
else if (line.StartsWith("eff=")) ParseInto(line.Substring(4), v => Math.Max(0.1, v), efficiency);
|
|
else if (line.StartsWith("pending=")) ParseInto(line.Substring(8), v => Math.Max(0, Math.Min(0.999, v)), pending);
|
|
}
|
|
}
|
|
|
|
public void ResetState()
|
|
{
|
|
Array.Clear(slots, 0, slots.Length);
|
|
Array.Clear(pending, 0, pending.Length);
|
|
for (int j = 0; j < WorkJobs.Count; j++) efficiency[j] = 1.0;
|
|
}
|
|
|
|
static string Join(int[] a)
|
|
{
|
|
var parts = new string[a.Length];
|
|
for (int i = 0; i < a.Length; i++) parts[i] = a[i].ToString(CultureInfo.InvariantCulture);
|
|
return string.Join(",", parts);
|
|
}
|
|
|
|
static string Join(double[] a)
|
|
{
|
|
var parts = new string[a.Length];
|
|
for (int i = 0; i < a.Length; i++) parts[i] = a[i].ToString("R", CultureInfo.InvariantCulture);
|
|
return string.Join(",", parts);
|
|
}
|
|
|
|
static void ParseInto(string csv, Func<double, int> conv, int[] target)
|
|
{
|
|
string[] parts = csv.Split(',');
|
|
for (int i = 0; i < target.Length && i < parts.Length; i++)
|
|
if (double.TryParse(parts[i], NumberStyles.Float, CultureInfo.InvariantCulture, out double v)) target[i] = conv(v);
|
|
}
|
|
|
|
static void ParseInto(string csv, Func<double, double> conv, double[] target)
|
|
{
|
|
string[] parts = csv.Split(',');
|
|
for (int i = 0; i < target.Length && i < parts.Length; i++)
|
|
if (double.TryParse(parts[i], NumberStyles.Float, CultureInfo.InvariantCulture, out double v)) target[i] = conv(v);
|
|
}
|
|
}
|