Move custom GOL management to GameModel

Also use RGB<uint8_t> for storing custom colors.
This commit is contained in:
Tamás Bálint Misius
2024-09-14 11:39:47 +02:00
parent 352c6227f6
commit c1a859c106
13 changed files with 154 additions and 123 deletions

View File

@@ -511,34 +511,6 @@ void Client::SaveAuthorInfo(Json::Value *saveInto)
}
}
bool AddCustomGol(String ruleString, String nameString, unsigned int highColor, unsigned int lowColor)
{
auto &prefs = GlobalPrefs::Ref();
auto customGOLTypes = prefs.Get("CustomGOL.Types", std::vector<ByteString>{});
std::vector<ByteString> newCustomGOLTypes;
bool nameTaken = false;
for (auto gol : customGOLTypes)
{
auto parts = gol.FromUtf8().PartitionBy(' ');
if (parts.size())
{
if (parts[0] == nameString)
{
nameTaken = true;
}
}
newCustomGOLTypes.push_back(gol);
}
if (nameTaken)
return false;
StringBuilder sb;
sb << nameString << " " << ruleString << " " << highColor << " " << lowColor;
newCustomGOLTypes.push_back(sb.Build().ToUtf8());
prefs.Set("CustomGOL.Types", newCustomGOLTypes);
return true;
}
String Client::DoMigration(ByteString fromDir, ByteString toDir)
{
if (fromDir.at(fromDir.length() - 1) != '/')

View File

@@ -105,5 +105,3 @@ public:
String DoMigration(ByteString fromDir, ByteString toDir);
};
bool AddCustomGol(String ruleString, String nameString, unsigned int highColor, unsigned int lowColor);

View File

@@ -24,6 +24,8 @@ struct alignas(alignof(uint32_t) > alignof(T) ? alignof(uint32_t) : alignof(T))
{
T Blue, Green, Red;
constexpr RGB() = default;
constexpr RGB(T r, T g, T b):
Blue(b),
Green(g),
@@ -121,6 +123,8 @@ struct alignas(alignof(uint32_t) > alignof(T) ? alignof(uint32_t) : alignof(T))
{
T Blue, Green, Red, Alpha;
constexpr RGBA() = default;
constexpr RGBA(T r, T g, T b, T a):
Blue(b),
Green(g),

View File

@@ -1719,9 +1719,9 @@ bool GameController::GetMouseClickRequired()
return gameModel->GetMouseClickRequired();
}
void GameController::RemoveCustomGOLType(const ByteString &identifier)
void GameController::RemoveCustomGol(const ByteString &identifier)
{
gameModel->RemoveCustomGOLType(identifier);
gameModel->RemoveCustomGol(identifier);
}
void GameController::BeforeSimDraw()

View File

@@ -201,7 +201,7 @@ public:
void RunUpdater(UpdateInfo info);
bool GetMouseClickRequired();
void RemoveCustomGOLType(const ByteString &identifier);
void RemoveCustomGol(const ByteString &identifier);
void BeforeSimDraw();
void AfterSimDraw();

View File

@@ -137,6 +137,7 @@ GameModel::GameModel():
currentUser = Client::Ref().GetAuthUser();
}
LoadCustomGol();
BuildMenus();
perfectCircle = prefs.Get("PerfectCircleBrush", true);
@@ -312,60 +313,10 @@ void GameModel::BuildMenus()
Tool * tempTool = AddTool<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);
}
for (auto &gd : sd.GetCustomGol())
{
auto &prefs = GlobalPrefs::Ref();
auto customGOLTypes = prefs.Get("CustomGOL.Types", std::vector<ByteString>{});
std::vector<ByteString> validatedCustomLifeTypes;
std::vector<CustomGOLData> newCustomGol;
bool removedAny = false;
for (auto gol : customGOLTypes)
{
auto parts = gol.FromUtf8().PartitionBy(' ');
if (parts.size() != 4)
{
removedAny = true;
continue;
}
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 = AddTool<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(), nullptr);
menuList[SC_LIFE]->AddTool(tempTool);
}
sd.SetCustomGOL(newCustomGol);
Tool * tempTool = AddTool<ElementTool>(PT_LIFE|PMAPID(gd.rule), gd.nameString, "Custom GOL type: " + SerialiseGOLRule(gd.rule), gd.colour1, "DEFAULT_PT_LIFECUST_"+gd.nameString.ToAscii(), nullptr);
menuList[SC_LIFE]->AddTool(tempTool);
}
//Build other menus from wall data
@@ -1664,28 +1615,129 @@ void GameModel::SetPerfectCircle(bool perfectCircle)
}
}
bool GameModel::RemoveCustomGOLType(const ByteString &identifier)
bool GameModel::AddCustomGol(String ruleString, String nameString, RGB<uint8_t> color1, RGB<uint8_t> color2)
{
if (auto gd = CheckCustomGol(ruleString, nameString, color1, color2))
{
auto &sd = SimulationData::Ref();
auto newCustomGol = sd.GetCustomGol();
newCustomGol.push_back(*gd);
sd.SetCustomGOL(newCustomGol);
SaveCustomGol();
BuildMenus();
return true;
}
return false;
}
bool GameModel::RemoveCustomGol(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)
std::vector<CustomGOLData> newCustomGol;
auto &sd = SimulationData::Ref();
for (auto gol : sd.GetCustomGol())
{
auto parts = gol.PartitionBy(' ');
if (parts.size() && "DEFAULT_PT_LIFECUST_" + parts[0] == identifier)
if ("DEFAULT_PT_LIFECUST_" + gol.nameString == identifier.FromUtf8())
{
removedAny = true;
}
else
newCustomGOLTypes.push_back(gol);
{
newCustomGol.push_back(gol);
}
}
if (removedAny)
{
prefs.Set("CustomGOL.Types", newCustomGOLTypes);
sd.SetCustomGOL(newCustomGol);
BuildMenus();
SaveCustomGol();
}
BuildMenus();
return removedAny;
}
void GameModel::LoadCustomGol()
{
auto &prefs = GlobalPrefs::Ref();
auto customGOLTypes = prefs.Get("CustomGOL.Types", std::vector<ByteString>{});
bool removedAny = false;
std::vector<CustomGOLData> newCustomGol;
for (auto gol : customGOLTypes)
{
auto parts = gol.FromUtf8().PartitionBy(' ');
if (parts.size() != 4)
{
removedAny = true;
continue;
}
auto nameString = parts[0];
auto ruleString = parts[1];
auto &colour1String = parts[2];
auto &colour2String = parts[3];
RGB<uint8_t> color1;
RGB<uint8_t> color2;
try
{
color1 = RGB<uint8_t>::Unpack(colour1String.ToNumber<int>());
color2 = RGB<uint8_t>::Unpack(colour2String.ToNumber<int>());
}
catch (std::exception &)
{
removedAny = true;
continue;
}
if (auto gd = CheckCustomGol(ruleString, nameString, color1, color2))
{
newCustomGol.push_back(*gd);
}
else
{
removedAny = true;
}
}
auto &sd = SimulationData::Ref();
sd.SetCustomGOL(newCustomGol);
if (removedAny)
{
SaveCustomGol();
}
}
void GameModel::SaveCustomGol()
{
auto &prefs = GlobalPrefs::Ref();
std::vector<ByteString> newCustomGOLTypes;
auto &sd = SimulationData::Ref();
for (auto &gd : sd.GetCustomGol())
{
StringBuilder sb;
sb << gd.nameString << " " << SerialiseGOLRule(gd.rule) << " " << gd.colour1.Pack() << " " << gd.colour2.Pack();
newCustomGOLTypes.push_back(sb.Build().ToUtf8());
}
prefs.Set("CustomGOL.Types", newCustomGOLTypes);
}
std::optional<CustomGOLData> GameModel::CheckCustomGol(String ruleString, String nameString, RGB<uint8_t> color1, RGB<uint8_t> color2)
{
if (!ValidateGOLName(nameString))
{
return std::nullopt;
}
auto rule = ParseGOLString(ruleString);
if (rule == -1)
{
return std::nullopt;
}
auto &sd = SimulationData::Ref();
for (auto &gd : sd.GetCustomGol())
{
if (gd.nameString == nameString)
{
return std::nullopt;
}
}
return CustomGOLData{ rule, color1, color2, nameString };
}
void GameModel::UpdateUpTo(int upTo)
{
if (upTo < sim->debug_nextToUpdate)

View File

@@ -3,6 +3,7 @@
#include "client/User.h"
#include "gui/interface/Point.h"
#include "graphics/RendererSettings.h"
#include "simulation/CustomGOLData.h"
#include <vector>
#include <deque>
#include <memory>
@@ -295,7 +296,11 @@ public:
void AddNotification(Notification * notification);
void RemoveNotification(Notification * notification);
bool RemoveCustomGOLType(const ByteString &identifier);
bool AddCustomGol(String ruleString, String nameString, RGB<uint8_t> color1, RGB<uint8_t> color2);
bool RemoveCustomGol(const ByteString &identifier);
void LoadCustomGol();
void SaveCustomGol();
std::optional<CustomGOLData> CheckCustomGol(String ruleString, String nameString, RGB<uint8_t> color1, RGB<uint8_t> color2);
ByteString SelectNextIdentifier;
int SelectNextTool;

View File

@@ -602,7 +602,7 @@ void GameView::NotifyToolListChanged(GameModel * sender)
else if (identifier.BeginsWith("DEFAULT_PT_LIFECUST_"))
{
new ConfirmPrompt("Remove custom GOL type", "Are you sure you want to remove " + identifier.Substr(20).FromUtf8() + "?", { [this, identifier]() {
c->RemoveCustomGOLType(identifier);
c->RemoveCustomGol(identifier);
} });
}
}

View File

@@ -159,9 +159,7 @@ void GOLWindow::validate()
prefs.Set("CustomGOL.Rule", ruleString);
}
auto color1 = (((highColour.Red << 8) | highColour.Green) << 8) | highColour.Blue;
auto color2 = (((lowColour.Red << 8) | lowColour.Green) << 8) | lowColour.Blue;
if (!AddCustomGol(ruleString, nameString, color1, color2))
if (!gameModel.AddCustomGol(ruleString, nameString, highColour.NoAlpha(), lowColour.NoAlpha()))
{
new ErrorMessage("Could not add GOL type", "Name already taken");
return;

View File

@@ -1489,13 +1489,13 @@ static int listCustomGol(lua_State *L)
lua_newtable(L);
tpt_lua_pushString(L, cgol.nameString);
lua_setfield(L, -2, "name");
tpt_lua_pushString(L, cgol.ruleString);
tpt_lua_pushString(L, SerialiseGOLRule(cgol.rule));
lua_setfield(L, -2, "rulestr");
lua_pushnumber(L, cgol.rule);
lua_setfield(L, -2, "rule");
lua_pushnumber(L, cgol.colour1);
lua_pushnumber(L, cgol.colour1.Pack());
lua_setfield(L, -2, "color1");
lua_pushnumber(L, cgol.colour2);
lua_pushnumber(L, cgol.colour2.Pack());
lua_setfield(L, -2, "color2");
lua_rawseti(L, -2, ++i);
}
@@ -1529,10 +1529,9 @@ static int addCustomGol(lua_State *L)
if (sd.GetCustomGOLByRule(rule))
return luaL_error(L, "This Custom GoL rule already exists");
if (!AddCustomGol(ruleString, nameString, color1, color2))
return luaL_error(L, "Duplicate name, cannot add");
auto *lsi = GetLSI();
lsi->gameModel->BuildMenus();
if (!lsi->gameModel->AddCustomGol(ruleString, nameString, RGB<uint8_t>::Unpack(color1), RGB<uint8_t>::Unpack(color2)))
return luaL_error(L, "Duplicate name, cannot add");
return 0;
}
@@ -1540,9 +1539,7 @@ static int removeCustomGol(lua_State *L)
{
auto *lsi = GetLSI();
ByteString nameString = tpt_lua_checkByteString(L, 1);
bool removedAny = lsi->gameModel->RemoveCustomGOLType("DEFAULT_PT_LIFECUST_" + nameString);
if (removedAny)
lsi->gameModel->BuildMenus();
bool removedAny = lsi->gameModel->RemoveCustomGol("DEFAULT_PT_LIFECUST_" + nameString);
lua_pushboolean(L, removedAny);
return 1;
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include "graphics/Pixel.h"
#include "common/String.h"
struct CustomGOLData
{
int rule;
RGB<uint8_t> colour1, colour2;
String nameString;
inline bool operator <(const CustomGOLData &other) const
{
return rule < other.rule;
}
};

View File

@@ -9,6 +9,7 @@
#include "Particle.h"
#include "WallType.h"
#include "graphics/gcache_item.h"
#include "CustomGOLData.h"
#include <cstdint>
#include <vector>
#include <array>
@@ -140,17 +141,6 @@ constexpr int NGT_BRAN = 23;
constexpr auto REPLACE_MODE = UINT32_C(0x00000001);
constexpr auto SPECIFIC_DELETE = UINT32_C(0x00000002);
struct CustomGOLData
{
int rule, colour1, colour2;
String nameString, ruleString;
inline bool operator <(const CustomGOLData &other) const
{
return rule < other.rule;
}
};
class SimulationData : public ExplicitSingleton<SimulationData>
{
public:

View File

@@ -111,8 +111,8 @@ static void create(ELEMENT_CREATE_FUNC_ARGS)
auto *cgol = sd.GetCustomGOLByRule(v);
if (cgol)
{
sim->parts[i].dcolour = cgol->colour1;
sim->parts[i].tmp = cgol->colour2;
sim->parts[i].dcolour = cgol->colour1.Pack();
sim->parts[i].tmp = cgol->colour2.Pack();
}
}
sim->parts[i].tmp2 = ((v >> 17) & 0xF) + 1;