Compare commits
20 Commits
383b386604
...
master
Author | SHA1 | Date | |
---|---|---|---|
9b5411bc43 | |||
9cfd157fd2 | |||
a559009ee5 | |||
381e31f663 | |||
be3b6d225c | |||
c8f7948246 | |||
1ae76e7674 | |||
f2cfefda0f | |||
1b7eccf810 | |||
402e0168ee | |||
b8a4c8e673 | |||
88453791bf | |||
e86220e95b | |||
1be867c792 | |||
4ecb26300f | |||
d6f4413ea9 | |||
2d847fa666 | |||
850968100e | |||
ab3463fa16 | |||
e0f72cc6ce |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.op
|
73
Classes/LayerConfig.as
Normal file
73
Classes/LayerConfig.as
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
class LayerConfig {
|
||||||
|
string Name;
|
||||||
|
string Path;
|
||||||
|
string Hash;
|
||||||
|
|
||||||
|
CGameUILayer::EUILayerType LayerType = CGameUILayer::EUILayerType::Normal;
|
||||||
|
string AttachId = '';
|
||||||
|
|
||||||
|
LayerConfig() {}
|
||||||
|
|
||||||
|
LayerConfig(const string &in _ParentPath, const Json::Value &in _Data) {
|
||||||
|
Path = Path::Join(_ParentPath, _Data['Path']);
|
||||||
|
Hash = _Data['Hash'];
|
||||||
|
|
||||||
|
const string Manialink = GetManialink();
|
||||||
|
Name = PacksManager::GetLayerName(Manialink);
|
||||||
|
|
||||||
|
if (Name.Length == 0) {
|
||||||
|
throw('Can\'t find Manialink name');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_Data.HasKey('LayerType') && _Data['LayerType'].GetType() == Json::Type::String) {
|
||||||
|
LayerType = CastEUILayerType(_Data['LayerType']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_Data.HasKey('AttachId') && _Data['AttachId'].GetType() == Json::Type::String) {
|
||||||
|
AttachId = _Data['AttachId'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetManialink() const {
|
||||||
|
if (!IO::FileExists(Path)) {
|
||||||
|
throw('File doesn\'t exists: "'+ Path +'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
IO::File File(Path, IO::FileMode::Read);
|
||||||
|
const string Manialink = File.ReadToEnd();
|
||||||
|
|
||||||
|
const string FileHash = Crypto::Sha256(Manialink);
|
||||||
|
|
||||||
|
if (Hash != FileHash) {
|
||||||
|
print('Cannot load layer "'+ Path +'": Invalid hash');
|
||||||
|
trace('info.json Layer hash: '+ Hash);
|
||||||
|
trace('file hash: '+ FileHash);
|
||||||
|
if (S_DisplayHashWarning) {
|
||||||
|
RenderManager::NotifyWarning('Cannot load layer "'+ Path +'": Invalid hash');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Meta::IsDeveloperMode()) {
|
||||||
|
throw('Cannot load layer "'+ Path +'": Invalid hash');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Manialink;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGameUILayer::EUILayerType CastEUILayerType(const string &in _LayerType) {
|
||||||
|
if (_LayerType == 'Normal') return CGameUILayer::EUILayerType::Normal;
|
||||||
|
if (_LayerType == 'ScoresTable') return CGameUILayer::EUILayerType::ScoresTable;
|
||||||
|
if (_LayerType == 'ScreenIn3d') return CGameUILayer::EUILayerType::ScreenIn3d;
|
||||||
|
if (_LayerType == 'AltMenu') return CGameUILayer::EUILayerType::AltMenu;
|
||||||
|
if (_LayerType == 'Markers') return CGameUILayer::EUILayerType::Markers;
|
||||||
|
if (_LayerType == 'CutScene') return CGameUILayer::EUILayerType::CutScene;
|
||||||
|
if (_LayerType == 'InGameMenu') return CGameUILayer::EUILayerType::InGameMenu;
|
||||||
|
if (_LayerType == 'EditorPlugin') return CGameUILayer::EUILayerType::EditorPlugin;
|
||||||
|
if (_LayerType == 'ManiaplanetPlugin') return CGameUILayer::EUILayerType::ManiaplanetPlugin;
|
||||||
|
if (_LayerType == 'ManiaplanetMenu') return CGameUILayer::EUILayerType::ManiaplanetMenu;
|
||||||
|
if (_LayerType == 'LoadingScreen') return CGameUILayer::EUILayerType::LoadingScreen;
|
||||||
|
|
||||||
|
throw('Invalid EUILayerType: '+ _LayerType);
|
||||||
|
return CGameUILayer::EUILayerType::Normal;
|
||||||
|
}
|
||||||
|
}
|
81
Classes/PackConfig.as
Normal file
81
Classes/PackConfig.as
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
class PackConfig {
|
||||||
|
string Name;
|
||||||
|
string Author;
|
||||||
|
string Version;
|
||||||
|
array<string> Modes;
|
||||||
|
array<LayerConfig> Layers;
|
||||||
|
array<string> UnloadModules;
|
||||||
|
|
||||||
|
PackConfig() {}
|
||||||
|
|
||||||
|
PackConfig(const string &in _ParentPath, const string &in _Json) {
|
||||||
|
const Json::Value@ Data = Json::Parse(_Json);
|
||||||
|
|
||||||
|
Name = Data['Name'];
|
||||||
|
Author = Data['Author'];
|
||||||
|
Version = Data['Version'];
|
||||||
|
|
||||||
|
if (Data.HasKey('Modes') && Data['Modes'].GetType() == Json::Type::Array) {
|
||||||
|
for (uint i = 0; i < Data['Modes'].Length; i++) {
|
||||||
|
Modes.InsertLast(Data['Modes'][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Data.HasKey('Layers') && Data['Layers'].GetType() == Json::Type::Array) {
|
||||||
|
for (uint i = 0; i < Data['Layers'].Length; i++) {
|
||||||
|
try {
|
||||||
|
const LayerConfig LayerConfig(_ParentPath, Data['Layers'][i]);
|
||||||
|
|
||||||
|
Layers.InsertLast(LayerConfig);
|
||||||
|
} catch {
|
||||||
|
warn("Can't load layer \""+ i +"\" of Interface Config: \""+ Name +"\"\nException Message: " + getExceptionInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Data.HasKey('UnloadModules') && Data['UnloadModules'].GetType() == Json::Type::Array) {
|
||||||
|
for (uint i = 0; i < Data['UnloadModules'].Length; i++) {
|
||||||
|
UnloadModules.InsertLast(Data['UnloadModules'][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string getPrettyModes() const {
|
||||||
|
if (Modes.Find('any') >= 0) return 'any';
|
||||||
|
|
||||||
|
const string Pattern = '(?:.*(?:/|\\\\|^))(.*)\\.Script\\.txt';
|
||||||
|
|
||||||
|
string Result;
|
||||||
|
|
||||||
|
for (uint i = 0; i < Modes.Length; i++) {
|
||||||
|
if (i > 0) Result += '\n';
|
||||||
|
|
||||||
|
const string Mode = Modes[i];
|
||||||
|
|
||||||
|
const array<string> Matches = Regex::Match(Mode, Pattern);
|
||||||
|
|
||||||
|
if (Matches.Length > 1) {
|
||||||
|
string Match = Matches[1];
|
||||||
|
|
||||||
|
if (Match.Contains('TM_')) {
|
||||||
|
array<string> Exploded = Match.Split('_');
|
||||||
|
Result += Exploded[1];
|
||||||
|
} else {
|
||||||
|
Result += Match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModeMatch(const string &in _SearchedMode) const {
|
||||||
|
const string SearchedMode = _SearchedMode.ToLower();
|
||||||
|
for (uint i = 0; i < Modes.Length; i++) {
|
||||||
|
const string Mode = Modes[i].ToLower();
|
||||||
|
if (Mode == "any" || Mode == SearchedMode) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
4
Hashes.as
Normal file
4
Hashes.as
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
const array<string> C_InterfacesPacksHashes = {
|
||||||
|
"c48796635bd234a22214336def4397bebb22a0f483ed6b30a4f412a1953be60e", // Anonymised TMWC 2024 - 1.0
|
||||||
|
"2a542ea483db750bb7a6240ea885e23eb34add0f88fbdfa38b5daa816d094cef" // Beacon Duo League - 1.0
|
||||||
|
};
|
226
PackManager.as
Normal file
226
PackManager.as
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
namespace PacksManager {
|
||||||
|
uint G_UILayersCount;
|
||||||
|
dictionary G_Configs;
|
||||||
|
array<string> G_EnabledConfigs;
|
||||||
|
|
||||||
|
void LoadConfigs() {
|
||||||
|
G_Configs = {};
|
||||||
|
|
||||||
|
const string Root = IO::FromStorageFolder('');
|
||||||
|
|
||||||
|
const array<string>@ Folders = IO::IndexFolder(Root, false);
|
||||||
|
for (uint i = 0; i < Folders.Length; i++) {
|
||||||
|
const string Path = Folders[i];
|
||||||
|
|
||||||
|
// Get Id
|
||||||
|
string Id;
|
||||||
|
const array<string> Parts = Path.Split('/');
|
||||||
|
for (uint j = Parts.Length - 1; j >= 0; j--) {
|
||||||
|
if (Parts[j].Length > 0) {
|
||||||
|
Id = Parts[j];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IO::FileExists(Path)) {
|
||||||
|
trace('Invalid Pack Config: "' + Path + '" is a file');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string InfoFile = Path::Join(Path, 'info.json');
|
||||||
|
if (!IO::FileExists(InfoFile)) {
|
||||||
|
trace('Invalid Pack Config: "' + Path + '" has no info.json file');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IO::File File(InfoFile, IO::FileMode::Read);
|
||||||
|
if (File.Size() <= 0) {
|
||||||
|
trace('Invalid Pack Config: "' + InfoFile + '" is not readable');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string FileContent = File.ReadToEnd();
|
||||||
|
|
||||||
|
const string InfoFileHash = Crypto::Sha256(FileContent);
|
||||||
|
|
||||||
|
if (C_InterfacesPacksHashes.Find(InfoFileHash) < 0) {
|
||||||
|
print('Invalid Pack Config "'+ Id +'": Invalid hash');
|
||||||
|
trace('info.json hash: '+ InfoFileHash);
|
||||||
|
if (S_DisplayHashWarning) {
|
||||||
|
RenderManager::NotifyWarning('Invalid Pack Config "'+ Id +'": Invalid hash');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Meta::IsDeveloperMode()) {
|
||||||
|
trace('Invalid Pack Config "'+ Id +'": Invalid hash');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const PackConfig Config(Path, FileContent);
|
||||||
|
G_Configs[Id] = Config;
|
||||||
|
} catch {
|
||||||
|
warn('Invalid Pack Config: "'+ Path +'" is an invalid\nException Message: '+ getExceptionInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (S_EnabledConfigs.Length > 0) {
|
||||||
|
G_EnabledConfigs = S_EnabledConfigs.Split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint i = 0; i < G_EnabledConfigs.Length; i++) {
|
||||||
|
const string Id = G_EnabledConfigs[i];
|
||||||
|
if (G_Configs.Exists(Id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderManager::NotifyWarning("Enabled interfaces " + Id + " has been deleted.");
|
||||||
|
G_EnabledConfigs.RemoveAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
InjectInterfacesIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InjectInterfacesIfNeeded() {
|
||||||
|
CTrackManiaNetwork@ Network = cast<CTrackManiaNetwork>(GetApp().Network);
|
||||||
|
CGameManiaAppPlayground@ ManiaApp = Network.ClientManiaAppPlayground;
|
||||||
|
CTrackManiaNetworkServerInfo@ ServerInfo = cast<CTrackManiaNetworkServerInfo>(Network.ServerInfo);
|
||||||
|
|
||||||
|
if (ManiaApp !is null) {
|
||||||
|
MwFastBuffer<CGameUILayer@> UILayers = ManiaApp.UILayers;
|
||||||
|
|
||||||
|
dictionary IndexedLayers = GetIndexedUILayers(UILayers);
|
||||||
|
|
||||||
|
for (uint i = 0; i < G_EnabledConfigs.Length; i++) {
|
||||||
|
const string Id = G_EnabledConfigs[i];
|
||||||
|
|
||||||
|
const PackConfig@ Config = PacksManager::GetConfig(Id);
|
||||||
|
|
||||||
|
|
||||||
|
if (!Config.ModeMatch(string(ServerInfo.CurScriptRelName))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint j = 0; j < Config.UnloadModules.Length; j++) {
|
||||||
|
const string ModuleId = Config.UnloadModules[j];
|
||||||
|
|
||||||
|
if (IndexedLayers.Exists(ModuleId)) {
|
||||||
|
print('Removing UI Layer: "' + ModuleId + '"');
|
||||||
|
CGameUILayer@ Layer = cast<CGameUILayer>(IndexedLayers[ModuleId]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some UILayer can have Maniascript that can interact with the game, even hidden.
|
||||||
|
* So it's safer to destroy them.
|
||||||
|
*/
|
||||||
|
ManiaApp.UILayerDestroy(Layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint j = 0; j < Config.Layers.Length; j++) {
|
||||||
|
const LayerConfig@ LayerConfig = Config.Layers[j];
|
||||||
|
|
||||||
|
CGameUILayer@ Layer;
|
||||||
|
if (IndexedLayers.Exists(LayerConfig.Name)) {
|
||||||
|
print('Updating Layer "'+ LayerConfig.Name +'"');
|
||||||
|
@Layer = cast<CGameUILayer>(IndexedLayers[LayerConfig.Name]);
|
||||||
|
} else {
|
||||||
|
print('Creating new Layer "'+ LayerConfig.Name +'"');
|
||||||
|
@Layer = ManiaApp.UILayerCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const string Manialink = LayerConfig.GetManialink();
|
||||||
|
Layer.ManialinkPage = Manialink;
|
||||||
|
Layer.AttachId = LayerConfig.AttachId;
|
||||||
|
Layer.Type = LayerConfig.LayerType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
G_UILayersCount = UILayers.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnableInterface(const string &in _Id) {
|
||||||
|
trace('Enabling Interface: '+ _Id);
|
||||||
|
|
||||||
|
const int Index = G_EnabledConfigs.Find(_Id);
|
||||||
|
if (Index >= 0) {
|
||||||
|
trace("Can't disable config \""+ _Id +"\": already enabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
G_EnabledConfigs.InsertLast(_Id);
|
||||||
|
S_EnabledConfigs = string::Join(G_EnabledConfigs, ',');
|
||||||
|
|
||||||
|
InjectInterfacesIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisableInterface(const string &in _Id) {
|
||||||
|
trace('Disabling Interface: '+ _Id);
|
||||||
|
|
||||||
|
const int Index = G_EnabledConfigs.Find(_Id);
|
||||||
|
if (Index < 0) {
|
||||||
|
trace("Can't disable config \""+ _Id +"\": not enabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
G_EnabledConfigs.RemoveAt(Index);
|
||||||
|
|
||||||
|
S_EnabledConfigs = string::Join(G_EnabledConfigs, ',');
|
||||||
|
}
|
||||||
|
|
||||||
|
PackConfig@ GetConfig(const string &in _Id) {
|
||||||
|
if (PacksManager::G_Configs.Exists(_Id)) {
|
||||||
|
return cast<PackConfig>(PacksManager::G_Configs[_Id]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionary GetIndexedUILayers(MwFastBuffer<CGameUILayer@> _UILayers) {
|
||||||
|
dictionary IndexedLayers;
|
||||||
|
|
||||||
|
for (uint i = 0; i < _UILayers.Length; i++) {
|
||||||
|
CGameUILayer@ UILayer = _UILayers[i];
|
||||||
|
|
||||||
|
const string Name = GetLayerName(UILayer.ManialinkPage);
|
||||||
|
if (Name.Length > 0) {
|
||||||
|
IndexedLayers[Name] = @UILayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return IndexedLayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetLayerName(const string &in _Manialink) {
|
||||||
|
array<string> FirstLines = _Manialink.Split("\n", 5);
|
||||||
|
|
||||||
|
if (FirstLines.Length > 0) {
|
||||||
|
for (uint j = 0; j < FirstLines.Length - 1; j++) {
|
||||||
|
if (FirstLines[j].Contains('<manialink ')) {
|
||||||
|
string[] match = Regex::Search(FirstLines[j], 'name="(\\w*)"');
|
||||||
|
if (match.Length == 2) {
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
void Yield() {
|
||||||
|
CTrackManiaNetwork@ Network = cast<CTrackManiaNetwork>(GetApp().Network);
|
||||||
|
|
||||||
|
if (Network.ClientManiaAppPlayground !is null) {
|
||||||
|
CGameManiaAppPlayground @ManiaApp = Network.ClientManiaAppPlayground;
|
||||||
|
MwFastBuffer<CGameUILayer@> UILayers = ManiaApp.UILayers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gamemode can inject UILayer at any time
|
||||||
|
* IMO, it's better to check count instead of gamemode name or anything else
|
||||||
|
*/
|
||||||
|
if (G_UILayersCount != UILayers.Length) {
|
||||||
|
G_UILayersCount = UILayers.Length;
|
||||||
|
InjectInterfacesIfNeeded();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
G_UILayersCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
README.md
Normal file
14
README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Custom Interfaces Loader
|
||||||
|
|
||||||
|
Custom Interfaces Loader is designed to load custom interfaces by casters for events using Nadeo Competition Tool or Live Servers.
|
||||||
|
|
||||||
|
This plugin requires an **Interfaces Pack**, which you can download below.
|
||||||
|
|
||||||
|
## How to Add an Interfaces Pack
|
||||||
|
|
||||||
|
1. Download the desired Interfaces Pack as a ZIP archive.
|
||||||
|
2. Open the Openplanet User Folder (press `F3`, go to **Openplanet → Open User Folder**).
|
||||||
|
3. Navigate to `PluginStorage/CustomInterfacesLoader`.
|
||||||
|
4. Extract the Interfaces Pack into this folder, maintaining the original file structure.
|
||||||
|
|
||||||
|
To verify successful installation, check that the extracted folder contains a file named `info.json`.
|
98
RenderManager.as
Normal file
98
RenderManager.as
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
namespace RenderManager {
|
||||||
|
bool G_RenderInterface = Meta::IsDeveloperMode(); // render by default in dev mode
|
||||||
|
float G_ButtonsDummySize = 0.;
|
||||||
|
|
||||||
|
void RenderInterface() {
|
||||||
|
if (G_RenderInterface) {
|
||||||
|
UI::SetNextWindowSize(550., 250.);
|
||||||
|
UI::Begin(Icons::Television + " Custom Interface Loader###CustomInterfaceLoader", G_RenderInterface, UI::WindowFlags::NoSavedSettings);
|
||||||
|
|
||||||
|
if (UI::Button(Icons::FolderOpen + ' Open File Explorer')) {
|
||||||
|
OpenExplorerPath(IO::FromStorageFolder(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
UI::SameLine();
|
||||||
|
|
||||||
|
if (UI::Button(Icons::Refresh + ' Reload')) {
|
||||||
|
PacksManager::LoadConfigs();
|
||||||
|
}
|
||||||
|
|
||||||
|
UI::SameLine();
|
||||||
|
|
||||||
|
vec2 LeftAlignedButtonsCursorPos = UI::GetCursorPos();
|
||||||
|
|
||||||
|
UI::Dummy(vec2(G_ButtonsDummySize, 1.));
|
||||||
|
|
||||||
|
UI::SameLine();
|
||||||
|
|
||||||
|
vec2 RightAlignedButtonsCursorPos = UI::GetCursorPos();
|
||||||
|
|
||||||
|
if (UI::Button(Icons::Info + ' Info')) {
|
||||||
|
OpenBrowserURL('https://openplanet.dev/plugin/' + Meta::ExecutingPlugin().SiteID);
|
||||||
|
}
|
||||||
|
UI::SameLine();
|
||||||
|
|
||||||
|
float RightAlignedButtonsWidth = UI::GetCursorPos().x - RightAlignedButtonsCursorPos.x;
|
||||||
|
|
||||||
|
float ScrollBarSize = 0.;
|
||||||
|
if (UI::GetScrollMaxY() > 0.) {
|
||||||
|
ScrollBarSize = UI::GetStyleVarFloat(UI::StyleVar::ScrollbarSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
G_ButtonsDummySize = Math::Max(0., UI::GetWindowSize().x - RightAlignedButtonsWidth - LeftAlignedButtonsCursorPos.x - UI::GetStyleVarVec2(UI::StyleVar::ItemSpacing).x - ScrollBarSize);
|
||||||
|
|
||||||
|
UI::Dummy(vec2());
|
||||||
|
|
||||||
|
if (UI::BeginTable('ConfigTable', 5, UI::TableFlags(UI::TableFlags::NoSavedSettings | UI::TableFlags::Resizable | UI::TableFlags::Sortable | UI::TableFlags::SizingStretchProp |UI::TableFlags::ScrollY ))) {
|
||||||
|
UI::TableSetupColumn("Name", UI::TableColumnFlags::DefaultSort, 200.);
|
||||||
|
UI::TableSetupColumn("Author", UI::TableColumnFlags::None, 150.);
|
||||||
|
UI::TableSetupColumn("Version", UI::TableColumnFlags::None, 100.);
|
||||||
|
UI::TableSetupColumn("Modes", UI::TableColumnFlags::None, 300.);
|
||||||
|
UI::TableSetupColumn("Enable", UI::TableColumnFlags::NoSort, 70.);
|
||||||
|
UI::TableHeadersRow();
|
||||||
|
|
||||||
|
const array<string> ConfigIds = PacksManager::G_Configs.GetKeys();
|
||||||
|
|
||||||
|
for (uint i = 0; i < ConfigIds.Length; i++) {
|
||||||
|
const string Id = ConfigIds[i];
|
||||||
|
const PackConfig@ Config = PacksManager::GetConfig(Id);
|
||||||
|
|
||||||
|
UI::TableNextRow();
|
||||||
|
UI::TableNextColumn();
|
||||||
|
UI::Text(Config.Name);
|
||||||
|
UI::TableNextColumn();
|
||||||
|
UI::Text(Config.Author);
|
||||||
|
UI::TableNextColumn();
|
||||||
|
UI::Text(Config.Version);
|
||||||
|
UI::TableNextColumn();
|
||||||
|
UI::Text(Config.getPrettyModes());
|
||||||
|
UI::TableNextColumn();
|
||||||
|
|
||||||
|
const int index = PacksManager::G_EnabledConfigs.Find(Id);
|
||||||
|
const bool enabled = (index >= 0);
|
||||||
|
const bool newValue = UI::Checkbox('###'+ Id, enabled);
|
||||||
|
if (newValue != enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
PacksManager::DisableInterface(Id);
|
||||||
|
} else {
|
||||||
|
PacksManager::EnableInterface(Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UI::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
UI::End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderMenu() {
|
||||||
|
if (UI::MenuItem(Icons::Television + ' Custom Interface Loader', "", G_RenderInterface)) {
|
||||||
|
G_RenderInterface = !G_RenderInterface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotifyWarning(const string &in _Message) {
|
||||||
|
UI::ShowNotification('Custom Interfaces Loader', _Message, vec4(0.9, 0.7, 0.2, 1.), 5000);
|
||||||
|
}
|
||||||
|
}
|
6
Settings.as
Normal file
6
Settings.as
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
[Setting name="Display hash mismatch warning"]
|
||||||
|
bool S_DisplayHashWarning = true;
|
||||||
|
|
||||||
|
[Setting hidden]
|
||||||
|
string S_EnabledConfigs = '';
|
@@ -1,6 +1,6 @@
|
|||||||
[meta]
|
[meta]
|
||||||
name = "Custom Interfaces Loader"
|
name = "Custom Interfaces Loader"
|
||||||
#siteid = 484
|
siteid = 671
|
||||||
author = "Beu"
|
author = "Beu"
|
||||||
category = "Interfaces"
|
category = "Interfaces"
|
||||||
version = "0.1"
|
version = "0.3"
|
151
main.as
151
main.as
@@ -1,151 +1,16 @@
|
|||||||
const array<string> C_ModuleToDisable = {
|
|
||||||
"UIModule_ChampionSpring2022_MatchInfo",
|
|
||||||
"UIModule_ChampionSpring2022_ScoresHeader",
|
|
||||||
"UIModule_ChampionSpring2022_Sponsors",
|
|
||||||
"UIModule_ChampionCup_LiveRanking",
|
|
||||||
"UIModule_ChampionCup_SpectatorInfo",
|
|
||||||
"UIModule_ChampionCup_Chat",
|
|
||||||
"UIModule_ChampionCup_MapInfo",
|
|
||||||
"UIModule_ChampionCup_Countdown",
|
|
||||||
"UIModule_Race_ScoresTable",
|
|
||||||
"UIModule_ChampionCup_Sign16x9_1",
|
|
||||||
"UIModule_ChampionCup_Sign16x9_2",
|
|
||||||
"UIModule_ChampionCup_Sign16x9_3",
|
|
||||||
"UIModule_ChampionCup_Sign16x9_4",
|
|
||||||
"UIModule_ChampionCup_Sign16x9_5",
|
|
||||||
"UIModule_ChampionCup_Sign64x10_64x10_Start",
|
|
||||||
"UIModule_ChampionCup_Sign64x10_64x10_Finish",
|
|
||||||
"UIModule_ChampionCup_Sign64x10_64x10_Checkpoint",
|
|
||||||
"UIModule_ChampionCup_Sign2x3_1"
|
|
||||||
};
|
|
||||||
|
|
||||||
void Main() {
|
void Main() {
|
||||||
auto Network = cast<CTrackManiaNetwork>(GetApp().Network);
|
PacksManager::LoadConfigs();
|
||||||
|
|
||||||
/**
|
while (true) {
|
||||||
* Unload already loaded custom UI
|
yield();
|
||||||
*/
|
PacksManager::Yield();
|
||||||
if (Network.ClientManiaAppPlayground !is null) {
|
|
||||||
CGameManiaAppPlayground @ManiaApp = Network.ClientManiaAppPlayground;
|
|
||||||
array<string> Manialinks = GetManialinkFiles();
|
|
||||||
|
|
||||||
array<string> CustomInterfacesIds;
|
|
||||||
|
|
||||||
for (uint i = 0; i < Manialinks.Length; i++) {
|
|
||||||
CustomInterfacesIds.InsertLast(Path::GetFileNameWithoutExtension(Manialinks[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
MwFastBuffer<CGameUILayer@> UILayers = ManiaApp.UILayers;
|
|
||||||
|
|
||||||
for (uint i = 0; i < UILayers.Length; i++) {
|
|
||||||
CGameUILayer @UILayer = UILayers[i];
|
|
||||||
string ManialinkPage = UILayer.ManialinkPage;
|
|
||||||
array<string> FirstLines = ManialinkPage.Split("\n", 5);
|
|
||||||
if (FirstLines.Length > 0) {
|
|
||||||
for (uint j = 0; j < FirstLines.Length - 1; j++) {
|
|
||||||
if (FirstLines[j].Contains('<manialink ')) {
|
|
||||||
string[] match = Regex::Search(FirstLines[j], 'name="(\\w*)"');
|
|
||||||
if (match.Length == 2) {
|
|
||||||
string LayerId = match[1];
|
|
||||||
|
|
||||||
if (LayerId.StartsWith('Custom_TMWC2024_')) {
|
|
||||||
if (CustomInterfacesIds.Find(LayerId) > -1) {
|
|
||||||
print("Updating existing layer " + LayerId);
|
|
||||||
UILayer.ManialinkPage = GetManialinkPage(LayerId);
|
|
||||||
} else {
|
|
||||||
print("Destroying not existing layer " + LayerId);
|
|
||||||
ManiaApp.UILayerDestroy(UILayer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint count = 0;
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
yield();
|
|
||||||
if (Network.ClientManiaAppPlayground !is null) {
|
|
||||||
CGameManiaAppPlayground @ManiaApp = Network.ClientManiaAppPlayground;
|
|
||||||
MwFastBuffer<CGameUILayer@> UILayers = ManiaApp.UILayers;
|
|
||||||
|
|
||||||
if (count != UILayers.Length) {
|
|
||||||
print('Walking through UILayers');
|
|
||||||
|
|
||||||
array<string> CustomUIModulesLoaded = {};
|
|
||||||
|
|
||||||
for (uint i = 0; i < UILayers.Length; i++) {
|
|
||||||
CGameUILayer @UILayer = UILayers[i];
|
|
||||||
string ManialinkPage = UILayer.ManialinkPage;
|
|
||||||
array<string> FirstLines = ManialinkPage.Split("\n", 5);
|
|
||||||
if (FirstLines.Length > 0) {
|
|
||||||
for (uint j = 0; j < FirstLines.Length - 1; j++) {
|
|
||||||
if (FirstLines[j].Contains('<manialink ')) {
|
|
||||||
string[] match = Regex::Search(FirstLines[j], 'name="(\\w*)"');
|
|
||||||
if (match.Length == 2) {
|
|
||||||
string id = match[1];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete UI
|
|
||||||
*/
|
|
||||||
if (C_ModuleToDisable.Find(id) > -1) {
|
|
||||||
print('Deleting ' + id);
|
|
||||||
ManiaApp.UILayerDestroy(UILayers[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id.StartsWith('Custom_TMWC2024_')) {
|
|
||||||
CustomUIModulesLoaded.InsertLast(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
array<string> Manialinks = GetManialinkFiles();
|
|
||||||
|
|
||||||
for (uint i = 0; i < Manialinks.Length; i++) {
|
|
||||||
string FilePath = Manialinks[i];
|
|
||||||
string FileName = Path::GetFileName(FilePath);
|
|
||||||
if (FileName.EndsWith('.xml')) {
|
|
||||||
string LayerId = Path::GetFileNameWithoutExtension(FileName);
|
|
||||||
if (CustomUIModulesLoaded.Find(LayerId) == -1) {
|
|
||||||
print("Loading layer " + LayerId);
|
|
||||||
CGameUILayer @UILayer = ManiaApp.UILayerCreate();
|
|
||||||
UILayer.ManialinkPage = GetManialinkPage(LayerId);
|
|
||||||
|
|
||||||
if (LayerId.Contains("_Sign16x9Small") || LayerId.Contains("_Sign155Small")) {
|
|
||||||
UILayer.Type = CGameUILayer::EUILayerType::ScreenIn3d;
|
|
||||||
UILayer.AttachId = "155_StadiumSmall";
|
|
||||||
} else if (LayerId.Contains("_Sign16x9") || LayerId.Contains("_Sign155")) {
|
|
||||||
UILayer.Type = CGameUILayer::EUILayerType::ScreenIn3d;
|
|
||||||
UILayer.AttachId = "155_Stadium";
|
|
||||||
} else if (LayerId.Contains("_Sign2x3")) {
|
|
||||||
UILayer.Type = CGameUILayer::EUILayerType::ScreenIn3d;
|
|
||||||
UILayer.AttachId = "2x3_Stadium";
|
|
||||||
} else if (LayerId.Contains("_Markers")) {
|
|
||||||
UILayer.Type = CGameUILayer::EUILayerType::Markers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
count = ManiaApp.UILayers.Length;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
array<string> GetManialinkFiles() {
|
void RenderInterface() {
|
||||||
return IO::IndexFolder(Meta::ExecutingPlugin().SourcePath + '/Manialinks/', false);
|
RenderManager::RenderInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
string GetManialinkPage(string _Id) {
|
void RenderMenu() {
|
||||||
IO::FileSource Xml('Manialinks/' + _Id + '.xml');
|
RenderManager::RenderMenu();
|
||||||
return Xml.ReadToEnd();
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user