|
|
using System;
|
|
|
using System.Diagnostics;
|
|
|
using System.Linq;
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using Microsoft.Xna.Framework;
|
|
|
|
|
|
using Encompass;
|
|
|
|
|
|
using isometricparkfna.Messages;
|
|
|
using isometricparkfna.Components;
|
|
|
|
|
|
namespace isometricparkfna.Engines
|
|
|
{
|
|
|
|
|
|
[Sends(typeof(SpawnContractMessage), typeof(SpawnDialogMessage), typeof(TickMessage))]
|
|
|
[Reads(typeof(AreaComponent),
|
|
|
typeof(BudgetComponent),
|
|
|
typeof(BudgetLineComponent),
|
|
|
typeof(ContractStatusComponent),
|
|
|
typeof(TreeDeltaComponent),
|
|
|
typeof(PreserveComponent))]
|
|
|
[Writes(typeof(BudgetComponent))]
|
|
|
[Receives(typeof(SingleExpenseMessage), typeof(DebugAlterTreesMessage))]
|
|
|
public class SimulationBridgeEngine : Engine
|
|
|
{
|
|
|
public Simulation simulation;
|
|
|
private int ticksToSend;
|
|
|
private Random random_generator;
|
|
|
|
|
|
Vector2[] all_squares;
|
|
|
|
|
|
public SimulationBridgeEngine(Simulation simulation)
|
|
|
{
|
|
|
this.simulation = simulation;
|
|
|
this.random_generator = new Random();
|
|
|
|
|
|
List<Vector2> squares = new List<Vector2>();
|
|
|
|
|
|
for(int i = 0; i < simulation.map.MapWidth; i++)
|
|
|
{
|
|
|
for(int j = 0; j < simulation.map.MapHeight; j++)
|
|
|
{
|
|
|
squares.Add(new Vector2(i,j));
|
|
|
}
|
|
|
}
|
|
|
this.all_squares = squares.ToArray();
|
|
|
}
|
|
|
|
|
|
public int addTick()
|
|
|
{
|
|
|
this.ticksToSend++;
|
|
|
return ticksToSend;
|
|
|
}
|
|
|
|
|
|
public override void Update(double dt)
|
|
|
{
|
|
|
|
|
|
foreach (ref readonly var message in ReadMessages<SingleExpenseMessage>())
|
|
|
{
|
|
|
this.simulation.AddConstruction(message.amount);
|
|
|
}
|
|
|
|
|
|
foreach (ref readonly var entity in ReadEntities<BudgetComponent>())
|
|
|
{
|
|
|
ref readonly var budgetComponent = ref GetComponent<BudgetComponent>(entity);
|
|
|
|
|
|
SetComponent(entity, new BudgetComponent
|
|
|
{
|
|
|
currentBudget = this.simulation.latestBudget,
|
|
|
priorBudget = this.simulation.previousBudget
|
|
|
});
|
|
|
}
|
|
|
|
|
|
decimal new_contract_amount = 0M;
|
|
|
decimal new_enforcement_amount = 0M;
|
|
|
decimal new_misc_amount = 0M;
|
|
|
|
|
|
foreach (ref readonly var entity in ReadEntities<BudgetLineComponent>())
|
|
|
{
|
|
|
ref readonly var budgetComponent = ref GetComponent<BudgetLineComponent>(entity);
|
|
|
|
|
|
// SetComponent(entity, new BudgetComponent{currentBudget = this.simulation.latestBudget,
|
|
|
// priorBudget = this.simulation.previousBudget});
|
|
|
switch (budgetComponent.category)
|
|
|
{
|
|
|
case "Contracts":
|
|
|
var status = GetComponent<ContractStatusComponent>(entity).status;
|
|
|
if (status == ContractStatus.Accepted)
|
|
|
{
|
|
|
new_contract_amount += budgetComponent.amount;
|
|
|
}
|
|
|
break;
|
|
|
case "Enforcement":
|
|
|
new_enforcement_amount += budgetComponent.amount;
|
|
|
break;
|
|
|
case "Misc":
|
|
|
new_misc_amount += budgetComponent.amount;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (this.ticksToSend > 0)
|
|
|
{
|
|
|
Logging.Trace(String.Format("{0} ticks to send in update", this.ticksToSend));
|
|
|
}
|
|
|
if (this.ticksToSend > 1)
|
|
|
{
|
|
|
Logging.Warning(String.Format("Simulating {0} ticks at once!", this.ticksToSend));
|
|
|
}
|
|
|
for (int i = ticksToSend; i > 0; i--)
|
|
|
{
|
|
|
|
|
|
#region calculate_preserve_cells
|
|
|
var preserve_cell_coordinates = new List<(int, int)>();
|
|
|
|
|
|
Stopwatch iterPreserves = new Stopwatch();
|
|
|
iterPreserves.Start();
|
|
|
|
|
|
foreach (ref readonly var entity in ReadEntities<PreserveComponent>()) {
|
|
|
ref readonly var areaComponent = ref GetComponent<AreaComponent>(entity);
|
|
|
|
|
|
foreach (var square in areaComponent.squares) {
|
|
|
preserve_cell_coordinates.Add(((int)square.X, (int)square.Y));
|
|
|
}
|
|
|
}
|
|
|
iterPreserves.Stop();
|
|
|
Logging.Info(String.Format("Preserve entities: {0:F3}", iterPreserves.Elapsed.TotalMilliseconds.ToString()));
|
|
|
|
|
|
|
|
|
Stopwatch iterCells = new Stopwatch();
|
|
|
iterCells.Start();
|
|
|
|
|
|
//This takes about 30ms versus the .0002 ms with no preserve.
|
|
|
//When the map is filled up with preserve, about 35ms and 9ms.
|
|
|
//With a handful of cells, it's more like 0.8ms
|
|
|
/*
|
|
|
for (int j = 0; j < this.simulation.PreserveCounts.GetLength(0); j++) {
|
|
|
for (int k = 0; k < this.simulation.PreserveCounts.GetLength(0); k++) {
|
|
|
count = 0;
|
|
|
foreach (var cell in this.simulation.map.iterate_neighbor_cells(j, k)) {
|
|
|
if (preserve_cells.Contains(cell)) {
|
|
|
count++;
|
|
|
}
|
|
|
}
|
|
|
this.simulation.PreserveCounts[j, k] = count;
|
|
|
}
|
|
|
}
|
|
|
//*/
|
|
|
|
|
|
//*
|
|
|
foreach ((var x, var y) in preserve_cell_coordinates) {
|
|
|
foreach ((var newx, var newy) in this.simulation.map.iterate_neighbor_cell_locations(x, y)) {
|
|
|
this.simulation.PreserveCounts[newx, newy] += 1;
|
|
|
}
|
|
|
}
|
|
|
//*/
|
|
|
iterCells.Stop();
|
|
|
Logging.Info(String.Format("Cell loop: {0:F3}", iterCells.Elapsed.TotalMilliseconds.ToString()));
|
|
|
|
|
|
#endregion
|
|
|
SendMessage<TickMessage>(new TickMessage { SimulationDateTime = simulation.DateTime });
|
|
|
//For now:
|
|
|
SendMessage<SpawnContractMessage>(new SpawnContractMessage {name = string.Format("#logging_company.capitalizeAll# {0}",
|
|
|
this.simulation.DateTime.ToShortDateString()) });
|
|
|
|
|
|
#region events
|
|
|
if (simulation.latestBudget.DateTime != default)
|
|
|
{
|
|
|
|
|
|
var tree_count_threshold = (int)(0.05 * this.simulation.map.tree_capacity);
|
|
|
|
|
|
if (simulation.latestBudget.money < 0M
|
|
|
&& simulation.previousBudget.money >= 0M)
|
|
|
{
|
|
|
SendMessage<SpawnDialogMessage>(new SpawnDialogMessage {Path = "EndLowFunds"});
|
|
|
}
|
|
|
else if (simulation.latestBudget.money < 10_000M
|
|
|
&& simulation.previousBudget.money >= 10_000M)
|
|
|
{
|
|
|
SendMessage<SpawnDialogMessage>(new SpawnDialogMessage {Path = "VeryLowFunds"});
|
|
|
}
|
|
|
else if (simulation.latestBudget.money < 25_000M
|
|
|
&& simulation.previousBudget.money >= 25_000M)
|
|
|
{
|
|
|
SendMessage<SpawnDialogMessage>(new SpawnDialogMessage {Path = "LowFunds"});
|
|
|
}
|
|
|
|
|
|
if ((simulation.latestBudget.trees < tree_count_threshold)
|
|
|
&& (simulation.previousBudget.trees >= tree_count_threshold))
|
|
|
{
|
|
|
SendMessage<SpawnDialogMessage>(new SpawnDialogMessage {Path = "PoorTreeHealth"});
|
|
|
}
|
|
|
|
|
|
Logging.Spy(new {count = preserve_cell_coordinates.Count(), half = (simulation.map.tree_capacity * 0.5)} );
|
|
|
|
|
|
if ((simulation.latestBudget.preserve_cells > (simulation.map.tree_capacity * 0.5))
|
|
|
&& (simulation.previousBudget.preserve_cells <= (simulation.map.tree_capacity * 0.5)))
|
|
|
{
|
|
|
SendMessage<SpawnDialogMessage>(new SpawnDialogMessage {Path = "PreserveHalf"});
|
|
|
var fundraiserEntity = CreateEntity();
|
|
|
|
|
|
AddComponent(fundraiserEntity, new BudgetLineComponent {
|
|
|
category = "Misc",
|
|
|
amount = 20_000M });
|
|
|
}
|
|
|
else if ((simulation.latestBudget.preserve_cells > (simulation.map.tree_capacity * 0.25))
|
|
|
&& (simulation.previousBudget.preserve_cells <= (simulation.map.tree_capacity * 0.25)))
|
|
|
{
|
|
|
SendMessage<SpawnDialogMessage>(new SpawnDialogMessage {Path = "PreserveProgress"});
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
foreach (ref readonly var entity in ReadEntities<TreeDeltaComponent>())
|
|
|
{
|
|
|
if ((HasComponent<ContractStatusComponent>(entity) && GetComponent<ContractStatusComponent>(entity).status == ContractStatus.Accepted)
|
|
|
|| !(HasComponent<ContractStatusComponent>(entity) ))
|
|
|
{
|
|
|
var delta = GetComponent<TreeDeltaComponent>(entity);
|
|
|
|
|
|
var squares = HasComponent<AreaComponent>(entity) ? GetComponent<AreaComponent>(entity).squares : this.all_squares;
|
|
|
var removed = 0;
|
|
|
var added = 0;
|
|
|
var tree_squares = squares.Where((square) => simulation.map.cells[(int)square.X][(int)square.Y].HasTree);
|
|
|
var to_remove = Math.Abs(delta.deltaTrees.Value);
|
|
|
|
|
|
//calculate the probability in order to get the expected number of removed trees
|
|
|
var expected = to_remove;
|
|
|
var trials = tree_squares.Count();
|
|
|
double probability = ((double)expected / (double)trials) / 12;
|
|
|
|
|
|
foreach (var square in tree_squares)
|
|
|
{
|
|
|
var cell = simulation.map.cells[(int)square.X][(int)square.Y];
|
|
|
if (cell.HasTree
|
|
|
&& random_generator.NextDouble() < probability)
|
|
|
{
|
|
|
if (delta.deltaTrees.Value < 0)
|
|
|
{
|
|
|
cell.RemoveTree();
|
|
|
removed++;
|
|
|
}
|
|
|
else if (delta.deltaTrees.Value > 0)
|
|
|
{
|
|
|
var random_type = random_generator.Next(0, 3);
|
|
|
cell.AddTree(this.simulation.DateTime, (CellMap.TreeType)random_type);
|
|
|
added++;
|
|
|
}
|
|
|
}
|
|
|
if (removed >= to_remove)
|
|
|
{
|
|
|
// break;
|
|
|
}
|
|
|
}
|
|
|
Logging.Info(String.Format("Destroyed {0} trees, expected {1}, P(plant)= {2}", removed, (expected / 12.0), probability));
|
|
|
Logging.Info(String.Format("Planted {0} trees, expected {1}, P(destroy)= {2}", added, (expected / 12.0), probability));
|
|
|
}
|
|
|
}
|
|
|
simulation.preserve_cells = preserve_cell_coordinates.Count();
|
|
|
}
|
|
|
this.ticksToSend = 0;
|
|
|
|
|
|
simulation.contracts = new_contract_amount;
|
|
|
simulation.enforcement = new_enforcement_amount;
|
|
|
simulation.misc = new_misc_amount;
|
|
|
|
|
|
#region debug
|
|
|
|
|
|
foreach (ref readonly var message in ReadMessages<DebugAlterTreesMessage>())
|
|
|
{
|
|
|
var trees = message.DeltaTrees;
|
|
|
|
|
|
//Probaby not very performant; used only for debug.
|
|
|
foreach (CellMap.Cell cell in this.simulation.map.iterate_cells().Where(c => c.HasTree).ToList().Shuffle())
|
|
|
{
|
|
|
if (trees > 0) {
|
|
|
trees -= 1;
|
|
|
cell.RemoveTree();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endregion
|
|
|
|
|
|
//Move this here?
|
|
|
// this.simulation.update(dt);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|
|
|
|