The ones that remain blocking are the ones that run on different threads; see Task, yet another big mess to clean up.
1715 lines
47 KiB
C++
1715 lines
47 KiB
C++
#include "GameModel.h"
|
|
#include "BitmapBrush.h"
|
|
#include "EllipseBrush.h"
|
|
#include "Favorite.h"
|
|
#include "Format.h"
|
|
#include "GameController.h"
|
|
#include "GameModelException.h"
|
|
#include "GameView.h"
|
|
#include "Menu.h"
|
|
#include "Notification.h"
|
|
#include "RectangleBrush.h"
|
|
#include "TriangleBrush.h"
|
|
#include "QuickOptions.h"
|
|
#include "lua/CommandInterface.h"
|
|
#include "prefs/GlobalPrefs.h"
|
|
#include "client/Client.h"
|
|
#include "client/GameSave.h"
|
|
#include "client/SaveFile.h"
|
|
#include "client/SaveInfo.h"
|
|
#include "client/http/ExecVoteRequest.h"
|
|
#include "common/platform/Platform.h"
|
|
#include "graphics/Renderer.h"
|
|
#include "simulation/Air.h"
|
|
#include "simulation/GOLString.h"
|
|
#include "simulation/gravity/Gravity.h"
|
|
#include "simulation/Simulation.h"
|
|
#include "simulation/Snapshot.h"
|
|
#include "simulation/SnapshotDelta.h"
|
|
#include "simulation/ElementClasses.h"
|
|
#include "simulation/ElementGraphics.h"
|
|
#include "simulation/ToolClasses.h"
|
|
#include "gui/game/DecorationTool.h"
|
|
#include "gui/interface/Engine.h"
|
|
#include "gui/dialogues/ErrorMessage.h"
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
#include <optional>
|
|
|
|
HistoryEntry::~HistoryEntry()
|
|
{
|
|
// * Needed because Snapshot and SnapshotDelta are incomplete types in GameModel.h,
|
|
// so the default dtor for ~HistoryEntry cannot be generated.
|
|
}
|
|
|
|
GameModel::GameModel():
|
|
activeMenu(-1),
|
|
currentBrush(0),
|
|
currentUser(0, ""),
|
|
toolStrength(1.0f),
|
|
historyPosition(0),
|
|
activeColourPreset(0),
|
|
colourSelector(false),
|
|
colour(255, 0, 0, 255),
|
|
edgeMode(0),
|
|
ambientAirTemp(R_TEMP + 273.15f),
|
|
decoSpace(0)
|
|
{
|
|
sim = new Simulation();
|
|
ren = new Renderer(sim);
|
|
|
|
activeTools = regularToolset;
|
|
|
|
std::fill(decoToolset, decoToolset+4, (Tool*)NULL);
|
|
std::fill(regularToolset, regularToolset+4, (Tool*)NULL);
|
|
|
|
//Default render prefs
|
|
ren->SetRenderMode({
|
|
RENDER_FIRE,
|
|
RENDER_EFFE,
|
|
RENDER_BASC,
|
|
});
|
|
ren->SetDisplayMode({});
|
|
ren->SetColourMode(0);
|
|
|
|
//Load config into renderer
|
|
auto &prefs = GlobalPrefs::Ref();
|
|
ren->SetColourMode(prefs.Get("Renderer.ColourMode", 0U));
|
|
|
|
auto displayModes = prefs.Get("Renderer.DisplayModes", std::vector<unsigned int>{});
|
|
if (displayModes.size())
|
|
{
|
|
ren->SetDisplayMode(displayModes);
|
|
}
|
|
auto renderModes = prefs.Get("Renderer.RenderModes", std::vector<unsigned int>{});
|
|
if (renderModes.size())
|
|
{
|
|
ren->SetRenderMode(renderModes);
|
|
}
|
|
|
|
ren->gravityFieldEnabled = prefs.Get("Renderer.GravityField", false);
|
|
ren->decorations_enable = prefs.Get("Renderer.Decorations", true);
|
|
|
|
//Load config into simulation
|
|
edgeMode = prefs.Get("Simulation.EdgeMode", 0); // TODO: EdgeMode enum
|
|
sim->SetEdgeMode(edgeMode);
|
|
ambientAirTemp = float(R_TEMP) + 273.15f;
|
|
{
|
|
auto temp = prefs.Get("Simulation.AmbientAirTemp", ambientAirTemp);
|
|
if (MIN_TEMP <= temp && MAX_TEMP >= temp)
|
|
{
|
|
ambientAirTemp = temp;
|
|
}
|
|
}
|
|
sim->air->ambientAirTemp = ambientAirTemp;
|
|
decoSpace = prefs.Get("Simulation.DecoSpace", 0); // TODO: DecoSpace enum
|
|
sim->SetDecoSpace(decoSpace);
|
|
int ngrav_enable = prefs.Get("Simulation.NewtonianGravity", 0); // TODO: NewtonianGravity enum
|
|
if (ngrav_enable)
|
|
sim->grav->start_grav_async();
|
|
sim->aheat_enable = prefs.Get("Simulation.AmbientHeat", 0); // TODO: AmbientHeat enum
|
|
sim->pretty_powder = prefs.Get("Simulation.PrettyPowder", 0); // TODO: PrettyPowder enum
|
|
|
|
Favorite::Ref().LoadFavoritesFromPrefs();
|
|
|
|
//Load last user
|
|
if(Client::Ref().GetAuthUser().UserID)
|
|
{
|
|
currentUser = Client::Ref().GetAuthUser();
|
|
}
|
|
|
|
BuildMenus();
|
|
|
|
perfectCircle = prefs.Get("PerfectCircleBrush", true);
|
|
BuildBrushList();
|
|
|
|
//Set default decoration colour
|
|
unsigned char colourR = std::max(std::min(prefs.Get("Decoration.Red", 200), 255), 0);
|
|
unsigned char colourG = std::max(std::min(prefs.Get("Decoration.Green", 100), 255), 0);
|
|
unsigned char colourB = std::max(std::min(prefs.Get("Decoration.Blue", 50), 255), 0);
|
|
unsigned char colourA = std::max(std::min(prefs.Get("Decoration.Alpha", 255), 255), 0);
|
|
|
|
SetColourSelectorColour(ui::Colour(colourR, colourG, colourB, colourA));
|
|
|
|
colourPresets.push_back(ui::Colour(255, 255, 255));
|
|
colourPresets.push_back(ui::Colour(0, 255, 255));
|
|
colourPresets.push_back(ui::Colour(255, 0, 255));
|
|
colourPresets.push_back(ui::Colour(255, 255, 0));
|
|
colourPresets.push_back(ui::Colour(255, 0, 0));
|
|
colourPresets.push_back(ui::Colour(0, 255, 0));
|
|
colourPresets.push_back(ui::Colour(0, 0, 255));
|
|
colourPresets.push_back(ui::Colour(0, 0, 0));
|
|
|
|
undoHistoryLimit = prefs.Get("Simulation.UndoHistoryLimit", 5U);
|
|
// cap due to memory usage (this is about 3.4GB of RAM)
|
|
if (undoHistoryLimit > 200)
|
|
SetUndoHistoryLimit(200);
|
|
|
|
mouseClickRequired = prefs.Get("MouseClickRequired", false);
|
|
includePressure = prefs.Get("Simulation.IncludePressure", true);
|
|
temperatureScale = prefs.Get("Renderer.TemperatureScale", 1); // TODO: TemperatureScale enum
|
|
|
|
ClearSimulation();
|
|
}
|
|
|
|
GameModel::~GameModel()
|
|
{
|
|
auto &prefs = GlobalPrefs::Ref();
|
|
{
|
|
//Save to config:
|
|
Prefs::DeferWrite dw(prefs);
|
|
prefs.Set("Renderer.ColourMode", ren->GetColourMode());
|
|
prefs.Set("Renderer.DisplayModes", ren->GetDisplayMode());
|
|
prefs.Set("Renderer.RenderModes", ren->GetRenderMode());
|
|
prefs.Set("Renderer.GravityField", (bool)ren->gravityFieldEnabled);
|
|
prefs.Set("Renderer.Decorations", (bool)ren->decorations_enable);
|
|
prefs.Set("Renderer.DebugMode", ren->debugLines); //These two should always be equivalent, even though they are different things
|
|
prefs.Set("Simulation.NewtonianGravity", sim->grav->IsEnabled());
|
|
prefs.Set("Simulation.AmbientHeat", sim->aheat_enable);
|
|
prefs.Set("Simulation.PrettyPowder", sim->pretty_powder);
|
|
prefs.Set("Decoration.Red", (int)colour.Red);
|
|
prefs.Set("Decoration.Green", (int)colour.Green);
|
|
prefs.Set("Decoration.Blue", (int)colour.Blue);
|
|
prefs.Set("Decoration.Alpha", (int)colour.Alpha);
|
|
}
|
|
|
|
for (size_t i = 0; i < menuList.size(); i++)
|
|
{
|
|
if (i == SC_FAVORITES)
|
|
menuList[i]->ClearTools();
|
|
delete menuList[i];
|
|
}
|
|
for (std::vector<Tool*>::iterator iter = extraElementTools.begin(), end = extraElementTools.end(); iter != end; ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
delete sim;
|
|
delete ren;
|
|
//if(activeTools)
|
|
// delete[] activeTools;
|
|
}
|
|
|
|
void GameModel::UpdateQuickOptions()
|
|
{
|
|
for(std::vector<QuickOption*>::iterator iter = quickOptions.begin(), end = quickOptions.end(); iter != end; ++iter)
|
|
{
|
|
QuickOption * option = *iter;
|
|
option->Update();
|
|
}
|
|
}
|
|
|
|
void GameModel::BuildQuickOptionMenu(GameController * controller)
|
|
{
|
|
for(std::vector<QuickOption*>::iterator iter = quickOptions.begin(), end = quickOptions.end(); iter != end; ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
quickOptions.clear();
|
|
|
|
quickOptions.push_back(new SandEffectOption(this));
|
|
quickOptions.push_back(new DrawGravOption(this));
|
|
quickOptions.push_back(new DecorationsOption(this));
|
|
quickOptions.push_back(new NGravityOption(this));
|
|
quickOptions.push_back(new AHeatOption(this));
|
|
quickOptions.push_back(new ConsoleShowOption(this, controller));
|
|
|
|
notifyQuickOptionsChanged();
|
|
UpdateQuickOptions();
|
|
}
|
|
|
|
void GameModel::BuildMenus()
|
|
{
|
|
int lastMenu = -1;
|
|
if(activeMenu != -1)
|
|
lastMenu = activeMenu;
|
|
|
|
ByteString activeToolIdentifiers[4];
|
|
if(regularToolset[0])
|
|
activeToolIdentifiers[0] = regularToolset[0]->Identifier;
|
|
if(regularToolset[1])
|
|
activeToolIdentifiers[1] = regularToolset[1]->Identifier;
|
|
if(regularToolset[2])
|
|
activeToolIdentifiers[2] = regularToolset[2]->Identifier;
|
|
if(regularToolset[3])
|
|
activeToolIdentifiers[3] = regularToolset[3]->Identifier;
|
|
|
|
//Empty current menus
|
|
for (size_t i = 0; i < menuList.size(); i++)
|
|
{
|
|
if (i == SC_FAVORITES)
|
|
menuList[i]->ClearTools();
|
|
delete menuList[i];
|
|
}
|
|
menuList.clear();
|
|
toolList.clear();
|
|
|
|
for(std::vector<Tool*>::iterator iter = extraElementTools.begin(), end = extraElementTools.end(); iter != end; ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
extraElementTools.clear();
|
|
elementTools.clear();
|
|
|
|
//Create menus
|
|
for (int i = 0; i < SC_TOTAL; i++)
|
|
{
|
|
menuList.push_back(new Menu(sim->msections[i].icon, sim->msections[i].name, sim->msections[i].doshow));
|
|
}
|
|
|
|
//Build menus from Simulation elements
|
|
for(int i = 0; i < PT_NUM; i++)
|
|
{
|
|
if(sim->elements[i].Enabled)
|
|
{
|
|
Tool * tempTool;
|
|
if(i == PT_LIGH)
|
|
{
|
|
tempTool = new Element_LIGH_Tool(i, sim->elements[i].Name, sim->elements[i].Description, sim->elements[i].Colour, sim->elements[i].Identifier, sim->elements[i].IconGenerator);
|
|
}
|
|
else if(i == PT_TESC)
|
|
{
|
|
tempTool = new Element_TESC_Tool(i, sim->elements[i].Name, sim->elements[i].Description, sim->elements[i].Colour, sim->elements[i].Identifier, sim->elements[i].IconGenerator);
|
|
}
|
|
else if(i == PT_STKM || i == PT_FIGH || i == PT_STKM2)
|
|
{
|
|
tempTool = new PlopTool(i, sim->elements[i].Name, sim->elements[i].Description, sim->elements[i].Colour, sim->elements[i].Identifier, sim->elements[i].IconGenerator);
|
|
}
|
|
else
|
|
{
|
|
tempTool = new ElementTool(i, sim->elements[i].Name, sim->elements[i].Description, sim->elements[i].Colour, sim->elements[i].Identifier, sim->elements[i].IconGenerator);
|
|
}
|
|
|
|
if (sim->elements[i].MenuSection >= 0 && sim->elements[i].MenuSection < SC_TOTAL && sim->elements[i].MenuVisible)
|
|
{
|
|
menuList[sim->elements[i].MenuSection]->AddTool(tempTool);
|
|
}
|
|
else
|
|
{
|
|
extraElementTools.push_back(tempTool);
|
|
}
|
|
elementTools.push_back(tempTool);
|
|
}
|
|
}
|
|
|
|
//Build menu for GOL types
|
|
for(int i = 0; i < NGOL; i++)
|
|
{
|
|
Tool * tempTool = new ElementTool(PT_LIFE|PMAPID(i), builtinGol[i].name, builtinGol[i].description, builtinGol[i].colour, "DEFAULT_PT_LIFE_"+builtinGol[i].name.ToAscii());
|
|
menuList[SC_LIFE]->AddTool(tempTool);
|
|
}
|
|
{
|
|
auto &prefs = GlobalPrefs::Ref();
|
|
auto customGOLTypes = prefs.Get("CustomGOL.Types", std::vector<ByteString>{});
|
|
std::vector<ByteString> validatedCustomLifeTypes;
|
|
std::vector<Simulation::CustomGOLData> newCustomGol;
|
|
bool removedAny = false;
|
|
for (auto gol : customGOLTypes)
|
|
{
|
|
auto parts = gol.FromUtf8().PartitionBy(' ');
|
|
if (parts.size() != 4)
|
|
{
|
|
removedAny = true;
|
|
continue;
|
|
}
|
|
Simulation::CustomGOLData gd;
|
|
gd.nameString = parts[0];
|
|
gd.ruleString = parts[1];
|
|
auto &colour1String = parts[2];
|
|
auto &colour2String = parts[3];
|
|
if (!ValidateGOLName(gd.nameString))
|
|
{
|
|
removedAny = true;
|
|
continue;
|
|
}
|
|
gd.rule = ParseGOLString(gd.ruleString);
|
|
if (gd.rule == -1)
|
|
{
|
|
removedAny = true;
|
|
continue;
|
|
}
|
|
try
|
|
{
|
|
gd.colour1 = colour1String.ToNumber<int>();
|
|
gd.colour2 = colour2String.ToNumber<int>();
|
|
}
|
|
catch (std::exception &)
|
|
{
|
|
removedAny = true;
|
|
continue;
|
|
}
|
|
newCustomGol.push_back(gd);
|
|
validatedCustomLifeTypes.push_back(gol);
|
|
}
|
|
if (removedAny)
|
|
{
|
|
// All custom rules that fail validation will be removed
|
|
prefs.Set("CustomGOL.Types", validatedCustomLifeTypes);
|
|
}
|
|
for (auto &gd : newCustomGol)
|
|
{
|
|
Tool * tempTool = new ElementTool(PT_LIFE|PMAPID(gd.rule), gd.nameString, "Custom GOL type: " + gd.ruleString, RGB<uint8_t>::Unpack(gd.colour1), "DEFAULT_PT_LIFECUST_"+gd.nameString.ToAscii(), NULL);
|
|
menuList[SC_LIFE]->AddTool(tempTool);
|
|
}
|
|
sim->SetCustomGOL(newCustomGol);
|
|
}
|
|
|
|
//Build other menus from wall data
|
|
for(int i = 0; i < UI_WALLCOUNT; i++)
|
|
{
|
|
Tool * tempTool = new WallTool(i, sim->wtypes[i].descs, sim->wtypes[i].colour, sim->wtypes[i].identifier, sim->wtypes[i].textureGen);
|
|
menuList[SC_WALL]->AddTool(tempTool);
|
|
//sim->wtypes[i]
|
|
}
|
|
|
|
//Build menu for tools
|
|
for (size_t i = 0; i < sim->tools.size(); i++)
|
|
{
|
|
Tool *tempTool = new Tool(
|
|
i,
|
|
sim->tools[i].Name,
|
|
sim->tools[i].Description,
|
|
sim->tools[i].Colour,
|
|
sim->tools[i].Identifier
|
|
);
|
|
menuList[SC_TOOL]->AddTool(tempTool);
|
|
}
|
|
//Add special sign and prop tools
|
|
menuList[SC_TOOL]->AddTool(new WindTool());
|
|
menuList[SC_TOOL]->AddTool(new PropertyTool(*this));
|
|
menuList[SC_TOOL]->AddTool(new SignTool(*this));
|
|
menuList[SC_TOOL]->AddTool(new SampleTool(*this));
|
|
menuList[SC_LIFE]->AddTool(new GOLTool(*this));
|
|
|
|
//Add decoration tools to menu
|
|
menuList[SC_DECO]->AddTool(new DecorationTool(*ren, DECO_ADD, "ADD", "Colour blending: Add.", 0x000000_rgb, "DEFAULT_DECOR_ADD"));
|
|
menuList[SC_DECO]->AddTool(new DecorationTool(*ren, DECO_SUBTRACT, "SUB", "Colour blending: Subtract.", 0x000000_rgb, "DEFAULT_DECOR_SUB"));
|
|
menuList[SC_DECO]->AddTool(new DecorationTool(*ren, DECO_MULTIPLY, "MUL", "Colour blending: Multiply.", 0x000000_rgb, "DEFAULT_DECOR_MUL"));
|
|
menuList[SC_DECO]->AddTool(new DecorationTool(*ren, DECO_DIVIDE, "DIV", "Colour blending: Divide." , 0x000000_rgb, "DEFAULT_DECOR_DIV"));
|
|
menuList[SC_DECO]->AddTool(new DecorationTool(*ren, DECO_SMUDGE, "SMDG", "Smudge tool, blends surrounding deco together.", 0x000000_rgb, "DEFAULT_DECOR_SMDG"));
|
|
menuList[SC_DECO]->AddTool(new DecorationTool(*ren, DECO_CLEAR, "CLR", "Erase any set decoration.", 0x000000_rgb, "DEFAULT_DECOR_CLR"));
|
|
menuList[SC_DECO]->AddTool(new DecorationTool(*ren, DECO_DRAW, "SET", "Draw decoration (No blending).", 0x000000_rgb, "DEFAULT_DECOR_SET"));
|
|
SetColourSelectorColour(colour); // update tool colors
|
|
decoToolset[0] = GetToolFromIdentifier("DEFAULT_DECOR_SET");
|
|
decoToolset[1] = GetToolFromIdentifier("DEFAULT_DECOR_CLR");
|
|
decoToolset[2] = GetToolFromIdentifier("DEFAULT_UI_SAMPLE");
|
|
decoToolset[3] = GetToolFromIdentifier("DEFAULT_PT_NONE");
|
|
|
|
regularToolset[0] = GetToolFromIdentifier(activeToolIdentifiers[0]);
|
|
regularToolset[1] = GetToolFromIdentifier(activeToolIdentifiers[1]);
|
|
regularToolset[2] = GetToolFromIdentifier(activeToolIdentifiers[2]);
|
|
regularToolset[3] = GetToolFromIdentifier(activeToolIdentifiers[3]);
|
|
|
|
//Set default tools
|
|
if (!regularToolset[0])
|
|
regularToolset[0] = GetToolFromIdentifier("DEFAULT_PT_DUST");
|
|
if (!regularToolset[1])
|
|
regularToolset[1] = GetToolFromIdentifier("DEFAULT_PT_NONE");
|
|
if (!regularToolset[2])
|
|
regularToolset[2] = GetToolFromIdentifier("DEFAULT_UI_SAMPLE");
|
|
if (!regularToolset[3])
|
|
regularToolset[3] = GetToolFromIdentifier("DEFAULT_PT_NONE");
|
|
|
|
lastTool = activeTools[0];
|
|
|
|
//Set default menu
|
|
activeMenu = SC_POWDERS;
|
|
|
|
if(lastMenu != -1)
|
|
activeMenu = lastMenu;
|
|
|
|
if(activeMenu != -1)
|
|
toolList = menuList[activeMenu]->GetToolList();
|
|
else
|
|
toolList = std::vector<Tool*>();
|
|
|
|
notifyMenuListChanged();
|
|
notifyToolListChanged();
|
|
notifyActiveToolsChanged();
|
|
notifyLastToolChanged();
|
|
|
|
//Build menu for favorites
|
|
BuildFavoritesMenu();
|
|
}
|
|
|
|
void GameModel::BuildFavoritesMenu()
|
|
{
|
|
menuList[SC_FAVORITES]->ClearTools();
|
|
|
|
std::vector<ByteString> favList = Favorite::Ref().GetFavoritesList();
|
|
for (size_t i = 0; i < favList.size(); i++)
|
|
{
|
|
Tool *tool = GetToolFromIdentifier(favList[i]);
|
|
if (tool)
|
|
menuList[SC_FAVORITES]->AddTool(tool);
|
|
}
|
|
|
|
if (activeMenu == SC_FAVORITES)
|
|
toolList = menuList[SC_FAVORITES]->GetToolList();
|
|
|
|
notifyMenuListChanged();
|
|
notifyToolListChanged();
|
|
notifyActiveToolsChanged();
|
|
notifyLastToolChanged();
|
|
}
|
|
|
|
void GameModel::BuildBrushList()
|
|
{
|
|
ui::Point radius{ 4, 4 };
|
|
if (brushList.size())
|
|
radius = brushList[currentBrush]->GetRadius();
|
|
brushList.clear();
|
|
|
|
brushList.push_back(std::make_unique<EllipseBrush>(perfectCircle));
|
|
brushList.push_back(std::make_unique<RectangleBrush>());
|
|
brushList.push_back(std::make_unique<TriangleBrush>());
|
|
|
|
//Load more from brushes folder
|
|
for (ByteString brushFile : Platform::DirectorySearch(BRUSH_DIR, "", { ".ptb" }))
|
|
{
|
|
std::vector<char> brushData;
|
|
if (!Platform::ReadFile(brushData, ByteString::Build(BRUSH_DIR, PATH_SEP_CHAR, brushFile)))
|
|
{
|
|
std::cout << "Brushes: Skipping " << brushFile << ". Could not open" << std::endl;
|
|
continue;
|
|
}
|
|
auto dimension = size_t(std::sqrt(brushData.size()));
|
|
if (dimension * dimension != brushData.size())
|
|
{
|
|
std::cout << "Brushes: Skipping " << brushFile << ". Invalid bitmap size" << std::endl;
|
|
continue;
|
|
}
|
|
brushList.push_back(std::make_unique<BitmapBrush>(ui::Point(dimension, dimension), reinterpret_cast<unsigned char const *>(brushData.data())));
|
|
}
|
|
|
|
brushList[currentBrush]->SetRadius(radius);
|
|
notifyBrushChanged();
|
|
}
|
|
|
|
Tool *GameModel::GetToolFromIdentifier(ByteString const &identifier)
|
|
{
|
|
for (auto *menu : menuList)
|
|
{
|
|
for (auto *tool : menu->GetToolList())
|
|
{
|
|
if (identifier == tool->Identifier)
|
|
{
|
|
return tool;
|
|
}
|
|
}
|
|
}
|
|
for (auto *extra : extraElementTools)
|
|
{
|
|
if (identifier == extra->Identifier)
|
|
{
|
|
return extra;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void GameModel::SetEdgeMode(int edgeMode)
|
|
{
|
|
this->edgeMode = edgeMode;
|
|
sim->SetEdgeMode(edgeMode);
|
|
}
|
|
|
|
int GameModel::GetEdgeMode()
|
|
{
|
|
return this->edgeMode;
|
|
}
|
|
|
|
void GameModel::SetTemperatureScale(int temperatureScale)
|
|
{
|
|
this->temperatureScale = temperatureScale;
|
|
}
|
|
|
|
void GameModel::SetAmbientAirTemperature(float ambientAirTemp)
|
|
{
|
|
this->ambientAirTemp = ambientAirTemp;
|
|
sim->air->ambientAirTemp = ambientAirTemp;
|
|
}
|
|
|
|
float GameModel::GetAmbientAirTemperature()
|
|
{
|
|
return this->ambientAirTemp;
|
|
}
|
|
|
|
void GameModel::SetDecoSpace(int decoSpace)
|
|
{
|
|
sim->SetDecoSpace(decoSpace);
|
|
this->decoSpace = sim->deco_space;
|
|
}
|
|
|
|
int GameModel::GetDecoSpace()
|
|
{
|
|
return this->decoSpace;
|
|
}
|
|
|
|
// * SnapshotDelta d is the difference between the two Snapshots A and B (i.e. d = B - A)
|
|
// if auto d = SnapshotDelta::FromSnapshots(A, B). In this case, a Snapshot that is
|
|
// identical to B can be constructed from d and A via d.Forward(A) (i.e. B = A + d)
|
|
// and a Snapshot that is identical to A can be constructed from d and B via
|
|
// d.Restore(B) (i.e. A = B - d). SnapshotDeltas often consume less memory than Snapshots,
|
|
// although pathological cases of pairs of Snapshots exist, the SnapshotDelta constructed
|
|
// from which actually consumes more than the two snapshots combined.
|
|
// * GameModel::history is an N-item deque of HistoryEntry structs, each of which owns either
|
|
// a SnapshotDelta, except for history[N-1], which always owns a Snapshot. A logical Snapshot
|
|
// accompanies each item in GameModel::history. This logical Snapshot may or may not be
|
|
// materialised (present in memory). If an item owns an actual Snapshot, the aforementioned
|
|
// logical Snapshot is this materialised Snapshot. If, however, an item owns a SnapshotDelta d,
|
|
// the accompanying logical Snapshot A is the Snapshot obtained via A = d.Restore(B), where B
|
|
// is the logical Snapshot accompanying the next (at an index that is one higher than the
|
|
// index of this item) item in history. Slightly more visually:
|
|
//
|
|
// i | history[i] | the logical Snapshot | relationships |
|
|
// | | accompanying history[i] | |
|
|
// -------|-----------------|-------------------------|---------------|
|
|
// | | | |
|
|
// N - 1 | Snapshot A | Snapshot A | A |
|
|
// | | | / |
|
|
// N - 2 | SnapshotDelta b | Snapshot B | B+b=A b-B |
|
|
// | | | / |
|
|
// N - 3 | SnapshotDelta c | Snapshot C | C+c=B c-C |
|
|
// | | | / |
|
|
// N - 4 | SnapshotDelta d | Snapshot D | D+d=C d-D |
|
|
// | | | / |
|
|
// ... | ... | ... | ... ... |
|
|
//
|
|
// * GameModel::historyPosition is an integer in the closed range 0 to N, which is decremented
|
|
// by GameModel::HistoryRestore and incremented by GameModel::HistoryForward, by 1 at a time.
|
|
// GameModel::historyCurrent "follows" historyPosition such that it always holds a Snapshot
|
|
// that is identical to the logical Snapshot of history[historyPosition], except when
|
|
// historyPosition = N, in which case it's empty. This following behaviour is achieved either
|
|
// by "stepping" historyCurrent by Forwarding and Restoring it via the SnapshotDelta in
|
|
// history[historyPosition], cloning the Snapshot in history[historyPosition] into it if
|
|
// historyPosition = N-1, or clearing if it historyPosition = N.
|
|
// * GameModel::historyCurrent is lost when a new Snapshot item is pushed into GameModel::history.
|
|
// This item appears wherever historyPosition currently points, and every other item above it
|
|
// is deleted. If historyPosition is below N, this gets rid of the Snapshot in history[N-1].
|
|
// Thus, N is set to historyPosition, after which the new Snapshot is pushed and historyPosition
|
|
// is incremented to the new N.
|
|
// * Pushing a new Snapshot into the history is a bit involved:
|
|
// * If there are no history entries yet, the new Snapshot is simply placed into GameModel::history.
|
|
// From now on, we consider cases in which GameModel::history is originally not empty.
|
|
//
|
|
// === after pushing Snapshot A' into the history
|
|
//
|
|
// i | history[i] | the logical Snapshot | relationships |
|
|
// | | accompanying history[i] | |
|
|
// -------|-----------------|-------------------------|---------------|
|
|
// | | | |
|
|
// 0 | Snapshot A | Snapshot A | A |
|
|
//
|
|
// * If there were discarded history entries (i.e. the user decided to continue from some state
|
|
// which they arrived to via at least one Ctrl+Z), history[N-2] is a SnapshotDelta that when
|
|
// Forwarded with the logical Snapshot of history[N-2] yields the logical Snapshot of history[N-1]
|
|
// from before the new item was pushed. This is not what we want, so we replace it with a
|
|
// SnapshotDelta that is the difference between the logical Snapshot of history[N-2] and the
|
|
// Snapshot freshly placed in history[N-1].
|
|
//
|
|
// === after pushing Snapshot A' into the history
|
|
//
|
|
// i | history[i] | the logical Snapshot | relationships |
|
|
// | | accompanying history[i] | |
|
|
// -------|-----------------|-------------------------|---------------|
|
|
// | | | |
|
|
// N - 1 | Snapshot A' | Snapshot A' | A' | b needs to be replaced with b',
|
|
// | | | / | B+b'=A'; otherwise we'd run
|
|
// N - 2 | SnapshotDelta b | Snapshot B | B+b=A b-B | into problems when trying to
|
|
// | | | / | reconstruct B from A' and b
|
|
// N - 3 | SnapshotDelta c | Snapshot C | C+c=B c-C | in HistoryRestore.
|
|
// | | | / |
|
|
// N - 4 | SnapshotDelta d | Snapshot D | D+d=C d-D |
|
|
// | | | / |
|
|
// ... | ... | ... | ... ... |
|
|
//
|
|
// === after replacing b with b'
|
|
//
|
|
// i | history[i] | the logical Snapshot | relationships |
|
|
// | | accompanying history[i] | |
|
|
// -------|-----------------|-------------------------|---------------|
|
|
// | | | |
|
|
// N - 1 | Snapshot A' | Snapshot A' | A' |
|
|
// | | | / |
|
|
// N - 2 | SnapshotDelta b'| Snapshot B | B+b'=A' b'-B |
|
|
// | | | / |
|
|
// N - 3 | SnapshotDelta c | Snapshot C | C+c=B c-C |
|
|
// | | | / |
|
|
// N - 4 | SnapshotDelta d | Snapshot D | D+d=C d-D |
|
|
// | | | / |
|
|
// ... | ... | ... | ... ... |
|
|
//
|
|
// * If there weren't any discarded history entries, history[N-2] is now also a Snapshot. Since
|
|
// the freshly pushed Snapshot in history[N-1] should be the only Snapshot in history, this is
|
|
// replaced with the SnapshotDelta that is the difference between history[N-2] and the Snapshot
|
|
// freshly placed in history[N-1].
|
|
//
|
|
// === after pushing Snapshot A' into the history
|
|
//
|
|
// i | history[i] | the logical Snapshot | relationships |
|
|
// | | accompanying history[i] | |
|
|
// -------|-----------------|-------------------------|---------------|
|
|
// | | | |
|
|
// N - 1 | Snapshot A' | Snapshot A' | A' | A needs to be converted to a,
|
|
// | | | | otherwise Snapshots would litter
|
|
// N - 1 | Snapshot A | Snapshot A | A | GameModel::history, which we
|
|
// | | | / | want to avoid because they
|
|
// N - 2 | SnapshotDelta b | Snapshot B | B+b=A b-B | waste a ton of memory
|
|
// | | | / |
|
|
// N - 3 | SnapshotDelta c | Snapshot C | C+c=B c-C |
|
|
// | | | / |
|
|
// N - 4 | SnapshotDelta d | Snapshot D | D+d=C d-D |
|
|
// | | | / |
|
|
// ... | ... | ... | ... ... |
|
|
//
|
|
// === after replacing A with a
|
|
//
|
|
// i | history[i] | the logical Snapshot | relationships |
|
|
// | | accompanying history[i] | |
|
|
// -------|-----------------|-------------------------|---------------|
|
|
// | | | |
|
|
// N - 1 | Snapshot A' | Snapshot A' | A' |
|
|
// | | | / |
|
|
// N - 1 | SnapshotDelta a | Snapshot A | A+a=A' a-A |
|
|
// | | | / |
|
|
// N - 2 | SnapshotDelta b | Snapshot B | B+b=A b-B |
|
|
// | | | / |
|
|
// N - 3 | SnapshotDelta c | Snapshot C | C+c=B c-C |
|
|
// | | | / |
|
|
// N - 4 | SnapshotDelta d | Snapshot D | D+d=C d-D |
|
|
// | | | / |
|
|
// ... | ... | ... | ... ... |
|
|
//
|
|
// * After all this, the front of the deque is truncated such that there are on more than
|
|
// undoHistoryLimit entries left.
|
|
|
|
const Snapshot *GameModel::HistoryCurrent() const
|
|
{
|
|
return historyCurrent.get();
|
|
}
|
|
|
|
bool GameModel::HistoryCanRestore() const
|
|
{
|
|
return historyPosition > 0U;
|
|
}
|
|
|
|
void GameModel::HistoryRestore()
|
|
{
|
|
if (!HistoryCanRestore())
|
|
{
|
|
return;
|
|
}
|
|
historyPosition -= 1U;
|
|
if (history[historyPosition].snap)
|
|
{
|
|
historyCurrent = std::make_unique<Snapshot>(*history[historyPosition].snap);
|
|
}
|
|
else
|
|
{
|
|
historyCurrent = history[historyPosition].delta->Restore(*historyCurrent);
|
|
}
|
|
}
|
|
|
|
bool GameModel::HistoryCanForward() const
|
|
{
|
|
return historyPosition < history.size();
|
|
}
|
|
|
|
void GameModel::HistoryForward()
|
|
{
|
|
if (!HistoryCanForward())
|
|
{
|
|
return;
|
|
}
|
|
historyPosition += 1U;
|
|
if (historyPosition == history.size())
|
|
{
|
|
historyCurrent = nullptr;
|
|
}
|
|
else if (history[historyPosition].snap)
|
|
{
|
|
historyCurrent = std::make_unique<Snapshot>(*history[historyPosition].snap);
|
|
}
|
|
else
|
|
{
|
|
historyCurrent = history[historyPosition - 1U].delta->Forward(*historyCurrent);
|
|
}
|
|
}
|
|
|
|
void GameModel::HistoryPush(std::unique_ptr<Snapshot> last)
|
|
{
|
|
Snapshot *rebaseOnto = nullptr;
|
|
if (historyPosition)
|
|
{
|
|
rebaseOnto = history.back().snap.get();
|
|
if (historyPosition < history.size())
|
|
{
|
|
historyCurrent = history[historyPosition - 1U].delta->Restore(*historyCurrent);
|
|
rebaseOnto = historyCurrent.get();
|
|
}
|
|
}
|
|
while (historyPosition < history.size())
|
|
{
|
|
history.pop_back();
|
|
}
|
|
if (rebaseOnto)
|
|
{
|
|
auto &prev = history.back();
|
|
prev.delta = SnapshotDelta::FromSnapshots(*rebaseOnto, *last);
|
|
prev.snap.reset();
|
|
}
|
|
history.emplace_back();
|
|
history.back().snap = std::move(last);
|
|
historyPosition += 1U;
|
|
historyCurrent.reset();
|
|
while (undoHistoryLimit < history.size())
|
|
{
|
|
history.pop_front();
|
|
historyPosition -= 1U;
|
|
}
|
|
}
|
|
|
|
unsigned int GameModel::GetUndoHistoryLimit()
|
|
{
|
|
return undoHistoryLimit;
|
|
}
|
|
|
|
void GameModel::SetUndoHistoryLimit(unsigned int undoHistoryLimit_)
|
|
{
|
|
undoHistoryLimit = undoHistoryLimit_;
|
|
GlobalPrefs::Ref().Set("Simulation.UndoHistoryLimit", undoHistoryLimit);
|
|
}
|
|
|
|
void GameModel::SetVote(int direction)
|
|
{
|
|
queuedVote = direction;
|
|
}
|
|
|
|
void GameModel::Tick()
|
|
{
|
|
if (execVoteRequest && execVoteRequest->CheckDone())
|
|
{
|
|
try
|
|
{
|
|
execVoteRequest->Finish();
|
|
currentSave->vote = execVoteRequest->Direction();
|
|
notifySaveChanged();
|
|
}
|
|
catch (const http::RequestError &ex)
|
|
{
|
|
new ErrorMessage("Error while voting", ByteString(ex.what()).FromUtf8());
|
|
}
|
|
execVoteRequest.reset();
|
|
}
|
|
if (!execVoteRequest && queuedVote)
|
|
{
|
|
if (currentSave)
|
|
{
|
|
execVoteRequest = std::make_unique<http::ExecVoteRequest>(currentSave->GetID(), *queuedVote);
|
|
execVoteRequest->Start();
|
|
}
|
|
queuedVote.reset();
|
|
}
|
|
}
|
|
|
|
Brush &GameModel::GetBrush()
|
|
{
|
|
return *brushList[currentBrush];
|
|
}
|
|
|
|
Brush *GameModel::GetBrushByID(int i)
|
|
{
|
|
if (i >= 0 && i < (int)brushList.size())
|
|
return brushList[i].get();
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
int GameModel::GetBrushID()
|
|
{
|
|
return currentBrush;
|
|
}
|
|
|
|
void GameModel::SetBrushID(int i)
|
|
{
|
|
currentBrush = i%brushList.size();
|
|
notifyBrushChanged();
|
|
}
|
|
|
|
void GameModel::AddObserver(GameView * observer){
|
|
observers.push_back(observer);
|
|
|
|
observer->NotifySimulationChanged(this);
|
|
observer->NotifyRendererChanged(this);
|
|
observer->NotifyPausedChanged(this);
|
|
observer->NotifySaveChanged(this);
|
|
observer->NotifyBrushChanged(this);
|
|
observer->NotifyMenuListChanged(this);
|
|
observer->NotifyToolListChanged(this);
|
|
observer->NotifyUserChanged(this);
|
|
observer->NotifyZoomChanged(this);
|
|
observer->NotifyColourSelectorVisibilityChanged(this);
|
|
observer->NotifyColourSelectorColourChanged(this);
|
|
observer->NotifyColourPresetsChanged(this);
|
|
observer->NotifyColourActivePresetChanged(this);
|
|
observer->NotifyQuickOptionsChanged(this);
|
|
observer->NotifyLastToolChanged(this);
|
|
UpdateQuickOptions();
|
|
}
|
|
|
|
void GameModel::SetToolStrength(float value)
|
|
{
|
|
toolStrength = value;
|
|
}
|
|
|
|
float GameModel::GetToolStrength()
|
|
{
|
|
return toolStrength;
|
|
}
|
|
|
|
void GameModel::SetActiveMenu(int menuID)
|
|
{
|
|
activeMenu = menuID;
|
|
toolList = menuList[menuID]->GetToolList();
|
|
notifyToolListChanged();
|
|
|
|
if(menuID == SC_DECO)
|
|
{
|
|
if(activeTools != decoToolset)
|
|
{
|
|
activeTools = decoToolset;
|
|
notifyActiveToolsChanged();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(activeTools != regularToolset)
|
|
{
|
|
activeTools = regularToolset;
|
|
notifyActiveToolsChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<Tool*> GameModel::GetUnlistedTools()
|
|
{
|
|
return extraElementTools;
|
|
}
|
|
|
|
std::vector<Tool*> GameModel::GetToolList()
|
|
{
|
|
return toolList;
|
|
}
|
|
|
|
int GameModel::GetActiveMenu()
|
|
{
|
|
return activeMenu;
|
|
}
|
|
|
|
//Get an element tool from an element ID
|
|
Tool * GameModel::GetElementTool(int elementID)
|
|
{
|
|
for(std::vector<Tool*>::iterator iter = elementTools.begin(), end = elementTools.end(); iter != end; ++iter)
|
|
{
|
|
if((*iter)->ToolID == elementID)
|
|
return *iter;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Tool * GameModel::GetActiveTool(int selection)
|
|
{
|
|
return activeTools[selection];
|
|
}
|
|
|
|
void GameModel::SetActiveTool(int selection, Tool * tool)
|
|
{
|
|
activeTools[selection] = tool;
|
|
notifyActiveToolsChanged();
|
|
}
|
|
|
|
std::vector<QuickOption*> GameModel::GetQuickOptions()
|
|
{
|
|
return quickOptions;
|
|
}
|
|
|
|
std::vector<Menu*> GameModel::GetMenuList()
|
|
{
|
|
return menuList;
|
|
}
|
|
|
|
SaveInfo *GameModel::GetSave() // non-owning
|
|
{
|
|
return currentSave.get();
|
|
}
|
|
|
|
std::unique_ptr<SaveInfo> GameModel::TakeSave()
|
|
{
|
|
// we don't notify listeners because we'll get a new save soon anyway
|
|
return std::move(currentSave);
|
|
}
|
|
|
|
void GameModel::SaveToSimParameters(const GameSave &saveData)
|
|
{
|
|
SetPaused(saveData.paused | GetPaused());
|
|
sim->gravityMode = saveData.gravityMode;
|
|
sim->customGravityX = saveData.customGravityX;
|
|
sim->customGravityY = saveData.customGravityY;
|
|
sim->air->airMode = saveData.airMode;
|
|
sim->air->ambientAirTemp = saveData.ambientAirTemp;
|
|
sim->edgeMode = saveData.edgeMode;
|
|
sim->legacy_enable = saveData.legacyEnable;
|
|
sim->water_equal_test = saveData.waterEEnabled;
|
|
sim->aheat_enable = saveData.aheatEnable;
|
|
if (saveData.gravityEnable && !sim->grav->IsEnabled())
|
|
{
|
|
sim->grav->start_grav_async();
|
|
}
|
|
else if (!saveData.gravityEnable && sim->grav->IsEnabled())
|
|
{
|
|
sim->grav->stop_grav_async();
|
|
}
|
|
sim->frameCount = saveData.frameCount;
|
|
if (saveData.hasRngState)
|
|
{
|
|
sim->rng.state(saveData.rngState);
|
|
}
|
|
else
|
|
{
|
|
sim->rng = RNG();
|
|
}
|
|
sim->ensureDeterminism = saveData.ensureDeterminism;
|
|
}
|
|
|
|
void GameModel::SetSave(std::unique_ptr<SaveInfo> newSave, bool invertIncludePressure)
|
|
{
|
|
currentSave = std::move(newSave);
|
|
currentFile.reset();
|
|
|
|
if (currentSave && currentSave->GetGameSave())
|
|
{
|
|
auto *saveData = currentSave->GetGameSave();
|
|
SaveToSimParameters(*saveData);
|
|
sim->clear_sim();
|
|
ren->ClearAccumulation();
|
|
sim->Load(saveData, !invertIncludePressure, { 0, 0 });
|
|
// This save was created before logging existed
|
|
// Add in the correct info
|
|
if (saveData->authors.size() == 0)
|
|
{
|
|
auto gameSave = currentSave->TakeGameSave();
|
|
gameSave->authors["type"] = "save";
|
|
gameSave->authors["id"] = currentSave->id;
|
|
gameSave->authors["username"] = currentSave->userName;
|
|
gameSave->authors["title"] = currentSave->name.ToUtf8();
|
|
gameSave->authors["description"] = currentSave->Description.ToUtf8();
|
|
gameSave->authors["published"] = (int)currentSave->Published;
|
|
gameSave->authors["date"] = currentSave->updatedDate;
|
|
currentSave->SetGameSave(std::move(gameSave));
|
|
}
|
|
// This save was probably just created, and we didn't know the ID when creating it
|
|
// Update with the proper ID
|
|
else if (saveData->authors.get("id", -1) == 0 || saveData->authors.get("id", -1) == -1)
|
|
{
|
|
auto gameSave = currentSave->TakeGameSave();
|
|
gameSave->authors["id"] = currentSave->id;
|
|
currentSave->SetGameSave(std::move(gameSave));
|
|
}
|
|
Client::Ref().OverwriteAuthorInfo(saveData->authors);
|
|
}
|
|
notifySaveChanged();
|
|
UpdateQuickOptions();
|
|
}
|
|
|
|
const SaveFile *GameModel::GetSaveFile() const
|
|
{
|
|
return currentFile.get();
|
|
}
|
|
|
|
std::unique_ptr<SaveFile> GameModel::TakeSaveFile()
|
|
{
|
|
// we don't notify listeners because we'll get a new save soon anyway
|
|
return std::move(currentFile);
|
|
}
|
|
|
|
void GameModel::SetSaveFile(std::unique_ptr<SaveFile> newSave, bool invertIncludePressure)
|
|
{
|
|
currentFile = std::move(newSave);
|
|
currentSave.reset();
|
|
|
|
if (currentFile && currentFile->GetGameSave())
|
|
{
|
|
auto *saveData = currentFile->GetGameSave();
|
|
SaveToSimParameters(*saveData);
|
|
sim->clear_sim();
|
|
ren->ClearAccumulation();
|
|
sim->Load(saveData, !invertIncludePressure, { 0, 0 });
|
|
Client::Ref().OverwriteAuthorInfo(saveData->authors);
|
|
}
|
|
|
|
notifySaveChanged();
|
|
UpdateQuickOptions();
|
|
}
|
|
|
|
Simulation * GameModel::GetSimulation()
|
|
{
|
|
return sim;
|
|
}
|
|
|
|
Renderer * GameModel::GetRenderer()
|
|
{
|
|
return ren;
|
|
}
|
|
|
|
User GameModel::GetUser()
|
|
{
|
|
return currentUser;
|
|
}
|
|
|
|
Tool * GameModel::GetLastTool()
|
|
{
|
|
return lastTool;
|
|
}
|
|
|
|
void GameModel::SetLastTool(Tool * newTool)
|
|
{
|
|
if(lastTool != newTool)
|
|
{
|
|
lastTool = newTool;
|
|
notifyLastToolChanged();
|
|
}
|
|
}
|
|
|
|
void GameModel::SetZoomEnabled(bool enabled)
|
|
{
|
|
ren->zoomEnabled = enabled;
|
|
notifyZoomChanged();
|
|
}
|
|
|
|
bool GameModel::GetZoomEnabled()
|
|
{
|
|
return ren->zoomEnabled;
|
|
}
|
|
|
|
void GameModel::SetZoomPosition(ui::Point position)
|
|
{
|
|
ren->zoomScopePosition = position;
|
|
notifyZoomChanged();
|
|
}
|
|
|
|
ui::Point GameModel::GetZoomPosition()
|
|
{
|
|
return ren->zoomScopePosition;
|
|
}
|
|
|
|
bool GameModel::MouseInZoom(ui::Point position)
|
|
{
|
|
if (!GetZoomEnabled())
|
|
return false;
|
|
|
|
int zoomFactor = GetZoomFactor();
|
|
ui::Point zoomWindowPosition = GetZoomWindowPosition();
|
|
ui::Point zoomWindowSize = ui::Point(GetZoomSize()*zoomFactor, GetZoomSize()*zoomFactor);
|
|
|
|
if (position.X >= zoomWindowPosition.X && position.Y >= zoomWindowPosition.Y && position.X < zoomWindowPosition.X+zoomWindowSize.X && position.Y < zoomWindowPosition.Y+zoomWindowSize.Y)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
ui::Point GameModel::AdjustZoomCoords(ui::Point position)
|
|
{
|
|
if (!GetZoomEnabled())
|
|
return position;
|
|
|
|
int zoomFactor = GetZoomFactor();
|
|
ui::Point zoomWindowPosition = GetZoomWindowPosition();
|
|
ui::Point zoomWindowSize = ui::Point(GetZoomSize()*zoomFactor, GetZoomSize()*zoomFactor);
|
|
|
|
if (position.X >= zoomWindowPosition.X && position.Y >= zoomWindowPosition.Y && position.X < zoomWindowPosition.X+zoomWindowSize.X && position.Y < zoomWindowPosition.Y+zoomWindowSize.Y)
|
|
return ((position-zoomWindowPosition)/GetZoomFactor())+GetZoomPosition();
|
|
return position;
|
|
}
|
|
|
|
void GameModel::SetZoomWindowPosition(ui::Point position)
|
|
{
|
|
ren->zoomWindowPosition = position;
|
|
notifyZoomChanged();
|
|
}
|
|
|
|
ui::Point GameModel::GetZoomWindowPosition()
|
|
{
|
|
return ren->zoomWindowPosition;
|
|
}
|
|
|
|
void GameModel::SetZoomSize(int size)
|
|
{
|
|
ren->zoomScopeSize = size;
|
|
notifyZoomChanged();
|
|
}
|
|
|
|
int GameModel::GetZoomSize()
|
|
{
|
|
return ren->zoomScopeSize;
|
|
}
|
|
|
|
void GameModel::SetZoomFactor(int factor)
|
|
{
|
|
ren->ZFACTOR = factor;
|
|
notifyZoomChanged();
|
|
}
|
|
|
|
int GameModel::GetZoomFactor()
|
|
{
|
|
return ren->ZFACTOR;
|
|
}
|
|
|
|
void GameModel::SetActiveColourPreset(size_t preset)
|
|
{
|
|
if (activeColourPreset-1 != preset)
|
|
activeColourPreset = preset+1;
|
|
else
|
|
{
|
|
activeTools[0] = GetToolFromIdentifier("DEFAULT_DECOR_SET");
|
|
notifyActiveToolsChanged();
|
|
}
|
|
notifyColourActivePresetChanged();
|
|
}
|
|
|
|
size_t GameModel::GetActiveColourPreset()
|
|
{
|
|
return activeColourPreset-1;
|
|
}
|
|
|
|
void GameModel::SetPresetColour(ui::Colour colour)
|
|
{
|
|
if (activeColourPreset > 0 && activeColourPreset <= colourPresets.size())
|
|
{
|
|
colourPresets[activeColourPreset-1] = colour;
|
|
notifyColourPresetsChanged();
|
|
}
|
|
}
|
|
|
|
std::vector<ui::Colour> GameModel::GetColourPresets()
|
|
{
|
|
return colourPresets;
|
|
}
|
|
|
|
void GameModel::SetColourSelectorVisibility(bool visibility)
|
|
{
|
|
if(colourSelector != visibility)
|
|
{
|
|
colourSelector = visibility;
|
|
notifyColourSelectorVisibilityChanged();
|
|
}
|
|
}
|
|
|
|
bool GameModel::GetColourSelectorVisibility()
|
|
{
|
|
return colourSelector;
|
|
}
|
|
|
|
void GameModel::SetColourSelectorColour(ui::Colour colour_)
|
|
{
|
|
colour = colour_;
|
|
|
|
std::vector<Tool*> tools = GetMenuList()[SC_DECO]->GetToolList();
|
|
for (auto tool : tools)
|
|
static_cast<DecorationTool *>(tool)->Colour = colour;
|
|
|
|
notifyColourSelectorColourChanged();
|
|
}
|
|
|
|
ui::Colour GameModel::GetColourSelectorColour()
|
|
{
|
|
return colour;
|
|
}
|
|
|
|
void GameModel::SetUser(User user)
|
|
{
|
|
currentUser = user;
|
|
//Client::Ref().SetAuthUser(user);
|
|
notifyUserChanged();
|
|
}
|
|
|
|
void GameModel::SetPaused(bool pauseState)
|
|
{
|
|
if (!pauseState && sim->debug_nextToUpdate > 0)
|
|
{
|
|
String logmessage = String::Build("Updated particles from #", sim->debug_nextToUpdate, " to end due to unpause");
|
|
UpdateUpTo(NPART);
|
|
Log(logmessage, false);
|
|
}
|
|
|
|
sim->sys_pause = pauseState?1:0;
|
|
notifyPausedChanged();
|
|
}
|
|
|
|
bool GameModel::GetPaused()
|
|
{
|
|
return sim->sys_pause?true:false;
|
|
}
|
|
|
|
void GameModel::SetDecoration(bool decorationState)
|
|
{
|
|
if (ren->decorations_enable != (decorationState?1:0))
|
|
{
|
|
ren->decorations_enable = decorationState?1:0;
|
|
notifyDecorationChanged();
|
|
UpdateQuickOptions();
|
|
if (decorationState)
|
|
SetInfoTip("Decorations Layer: On");
|
|
else
|
|
SetInfoTip("Decorations Layer: Off");
|
|
}
|
|
}
|
|
|
|
bool GameModel::GetDecoration()
|
|
{
|
|
return ren->decorations_enable?true:false;
|
|
}
|
|
|
|
void GameModel::SetAHeatEnable(bool aHeat)
|
|
{
|
|
sim->aheat_enable = aHeat;
|
|
UpdateQuickOptions();
|
|
if (aHeat)
|
|
SetInfoTip("Ambient Heat: On");
|
|
else
|
|
SetInfoTip("Ambient Heat: Off");
|
|
}
|
|
|
|
bool GameModel::GetAHeatEnable()
|
|
{
|
|
return sim->aheat_enable;
|
|
}
|
|
|
|
void GameModel::ResetAHeat()
|
|
{
|
|
sim->air->ClearAirH();
|
|
}
|
|
|
|
void GameModel::SetNewtonianGravity(bool newtonainGravity)
|
|
{
|
|
if (newtonainGravity)
|
|
{
|
|
sim->grav->start_grav_async();
|
|
SetInfoTip("Newtonian Gravity: On");
|
|
}
|
|
else
|
|
{
|
|
sim->grav->stop_grav_async();
|
|
SetInfoTip("Newtonian Gravity: Off");
|
|
}
|
|
UpdateQuickOptions();
|
|
}
|
|
|
|
bool GameModel::GetNewtonianGrvity()
|
|
{
|
|
return sim->grav->IsEnabled();
|
|
}
|
|
|
|
void GameModel::ShowGravityGrid(bool showGrid)
|
|
{
|
|
ren->gravityFieldEnabled = showGrid;
|
|
if (showGrid)
|
|
SetInfoTip("Gravity Grid: On");
|
|
else
|
|
SetInfoTip("Gravity Grid: Off");
|
|
}
|
|
|
|
bool GameModel::GetGravityGrid()
|
|
{
|
|
return ren->gravityFieldEnabled;
|
|
}
|
|
|
|
void GameModel::FrameStep(int frames)
|
|
{
|
|
sim->framerender += frames;
|
|
}
|
|
|
|
void GameModel::ClearSimulation()
|
|
{
|
|
//Load defaults
|
|
sim->gravityMode = 0;
|
|
sim->customGravityX = 0.0f;
|
|
sim->customGravityY = 0.0f;
|
|
sim->air->airMode = 0;
|
|
sim->legacy_enable = false;
|
|
sim->water_equal_test = false;
|
|
sim->SetEdgeMode(edgeMode);
|
|
sim->air->ambientAirTemp = ambientAirTemp;
|
|
|
|
sim->clear_sim();
|
|
ren->ClearAccumulation();
|
|
Client::Ref().ClearAuthorInfo();
|
|
|
|
notifySaveChanged();
|
|
UpdateQuickOptions();
|
|
}
|
|
|
|
void GameModel::SetPlaceSave(std::unique_ptr<GameSave> save)
|
|
{
|
|
transformedPlaceSave.reset();
|
|
placeSave = std::move(save);
|
|
notifyPlaceSaveChanged();
|
|
}
|
|
|
|
void GameModel::TransformPlaceSave(Mat2<int> transform, Vec2<int> nudge)
|
|
{
|
|
if (placeSave)
|
|
{
|
|
transformedPlaceSave = std::make_unique<GameSave>(*placeSave);
|
|
transformedPlaceSave->Transform(transform, nudge);
|
|
}
|
|
notifyTransformedPlaceSaveChanged();
|
|
}
|
|
|
|
void GameModel::SetClipboard(std::unique_ptr<GameSave> save)
|
|
{
|
|
clipboard = std::move(save);
|
|
}
|
|
|
|
const GameSave *GameModel::GetClipboard() const
|
|
{
|
|
return clipboard.get();
|
|
}
|
|
|
|
const GameSave *GameModel::GetTransformedPlaceSave() const
|
|
{
|
|
return transformedPlaceSave.get();
|
|
}
|
|
|
|
void GameModel::Log(String message, bool printToFile)
|
|
{
|
|
consoleLog.push_front(message);
|
|
if(consoleLog.size()>100)
|
|
consoleLog.pop_back();
|
|
notifyLogChanged(message);
|
|
if (printToFile)
|
|
std::cout << message.ToUtf8() << std::endl;
|
|
}
|
|
|
|
std::deque<String> GameModel::GetLog()
|
|
{
|
|
return consoleLog;
|
|
}
|
|
|
|
std::vector<Notification*> GameModel::GetNotifications()
|
|
{
|
|
return notifications;
|
|
}
|
|
|
|
void GameModel::AddNotification(Notification * notification)
|
|
{
|
|
notifications.push_back(notification);
|
|
notifyNotificationsChanged();
|
|
}
|
|
|
|
void GameModel::RemoveNotification(Notification * notification)
|
|
{
|
|
for(std::vector<Notification*>::iterator iter = notifications.begin(); iter != notifications.end(); ++iter)
|
|
{
|
|
if(*iter == notification)
|
|
{
|
|
delete *iter;
|
|
notifications.erase(iter);
|
|
break;
|
|
}
|
|
}
|
|
notifyNotificationsChanged();
|
|
}
|
|
|
|
void GameModel::SetToolTip(String text)
|
|
{
|
|
toolTip = text;
|
|
notifyToolTipChanged();
|
|
}
|
|
|
|
void GameModel::SetInfoTip(String text)
|
|
{
|
|
infoTip = text;
|
|
notifyInfoTipChanged();
|
|
}
|
|
|
|
String GameModel::GetToolTip()
|
|
{
|
|
return toolTip;
|
|
}
|
|
|
|
String GameModel::GetInfoTip()
|
|
{
|
|
return infoTip;
|
|
}
|
|
|
|
void GameModel::notifyNotificationsChanged()
|
|
{
|
|
for (std::vector<GameView*>::iterator iter = observers.begin(); iter != observers.end(); ++iter)
|
|
{
|
|
(*iter)->NotifyNotificationsChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyColourPresetsChanged()
|
|
{
|
|
for (std::vector<GameView*>::iterator iter = observers.begin(); iter != observers.end(); ++iter)
|
|
{
|
|
(*iter)->NotifyColourPresetsChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyColourActivePresetChanged()
|
|
{
|
|
for (std::vector<GameView*>::iterator iter = observers.begin(); iter != observers.end(); ++iter)
|
|
{
|
|
(*iter)->NotifyColourActivePresetChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyColourSelectorColourChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyColourSelectorColourChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyColourSelectorVisibilityChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyColourSelectorVisibilityChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyRendererChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyRendererChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifySaveChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifySaveChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifySimulationChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifySimulationChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyPausedChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyPausedChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyDecorationChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
//observers[i]->NotifyPausedChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyBrushChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyBrushChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyMenuListChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyMenuListChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyToolListChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyToolListChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyActiveToolsChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyActiveToolsChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyUserChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyUserChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyZoomChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyZoomChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyPlaceSaveChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyPlaceSaveChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyTransformedPlaceSaveChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyTransformedPlaceSaveChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyLogChanged(String entry)
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyLogChanged(this, entry);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyInfoTipChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyInfoTipChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyToolTipChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyToolTipChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyQuickOptionsChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyQuickOptionsChanged(this);
|
|
}
|
|
}
|
|
|
|
void GameModel::notifyLastToolChanged()
|
|
{
|
|
for (size_t i = 0; i < observers.size(); i++)
|
|
{
|
|
observers[i]->NotifyLastToolChanged(this);
|
|
}
|
|
}
|
|
|
|
bool GameModel::GetMouseClickRequired()
|
|
{
|
|
return mouseClickRequired;
|
|
}
|
|
|
|
void GameModel::SetMouseClickRequired(bool mouseClickRequired)
|
|
{
|
|
this->mouseClickRequired = mouseClickRequired;
|
|
}
|
|
|
|
bool GameModel::GetIncludePressure()
|
|
{
|
|
return includePressure;
|
|
}
|
|
|
|
void GameModel::SetIncludePressure(bool includePressure)
|
|
{
|
|
this->includePressure = includePressure;
|
|
}
|
|
|
|
void GameModel::SetPerfectCircle(bool perfectCircle)
|
|
{
|
|
if (perfectCircle != this->perfectCircle)
|
|
{
|
|
this->perfectCircle = perfectCircle;
|
|
BuildBrushList();
|
|
}
|
|
}
|
|
|
|
bool GameModel::RemoveCustomGOLType(const ByteString &identifier)
|
|
{
|
|
bool removedAny = false;
|
|
auto &prefs = GlobalPrefs::Ref();
|
|
auto customGOLTypes = prefs.Get("CustomGOL.Types", std::vector<ByteString>{});
|
|
std::vector<ByteString> newCustomGOLTypes;
|
|
for (auto gol : customGOLTypes)
|
|
{
|
|
auto parts = gol.PartitionBy(' ');
|
|
if (parts.size() && "DEFAULT_PT_LIFECUST_" + parts[0] == identifier)
|
|
removedAny = true;
|
|
else
|
|
newCustomGOLTypes.push_back(gol);
|
|
}
|
|
if (removedAny)
|
|
{
|
|
prefs.Set("CustomGOL.Types", newCustomGOLTypes);
|
|
}
|
|
BuildMenus();
|
|
return removedAny;
|
|
}
|
|
|
|
void GameModel::UpdateUpTo(int upTo)
|
|
{
|
|
if (upTo < sim->debug_nextToUpdate)
|
|
{
|
|
upTo = NPART;
|
|
}
|
|
if (sim->debug_nextToUpdate == 0)
|
|
{
|
|
BeforeSim();
|
|
}
|
|
sim->UpdateParticles(sim->debug_nextToUpdate, upTo);
|
|
if (upTo < NPART)
|
|
{
|
|
sim->debug_nextToUpdate = upTo;
|
|
}
|
|
else
|
|
{
|
|
AfterSim();
|
|
sim->debug_nextToUpdate = 0;
|
|
}
|
|
}
|
|
|
|
void GameModel::BeforeSim()
|
|
{
|
|
if (!sim->sys_pause || sim->framerender)
|
|
{
|
|
commandInterface->HandleEvent(BeforeSimEvent{});
|
|
}
|
|
sim->BeforeSim();
|
|
}
|
|
|
|
void GameModel::AfterSim()
|
|
{
|
|
sim->AfterSim();
|
|
commandInterface->HandleEvent(AfterSimEvent{});
|
|
}
|