Skip to content

Commit

Permalink
community requested features
Browse files Browse the repository at this point in the history
  • Loading branch information
KoloInDaCrib committed Feb 27, 2025
1 parent 53b79b9 commit 5fb7b4a
Show file tree
Hide file tree
Showing 8 changed files with 392 additions and 219 deletions.
216 changes: 170 additions & 46 deletions source/funkin/ui/debug/mods/ModsSelectState.hx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import haxe.ui.components.Button;
import haxe.ui.containers.VBox;
import haxe.ui.containers.windows.WindowManager;
import haxe.ui.tooltips.ToolTipManager;
import polymod.util.DependencyUtil;
import polymod.Polymod.ModMetadata;

using StringTools;

@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/mod-select/main-view.xml"))
class ModsSelectState extends UISubState
Expand All @@ -27,11 +31,16 @@ class ModsSelectState extends UISubState
var prevPersistentDraw:Bool;
var prevPersistentUpdate:Bool;

/**
* A list for all enabled mods. The main point of this list is to provide priorities of enabled mods without focusing on dependencies.
* Polymod handles sorting by dependencies internally upon loading mods.
*/
var changeableModList:Array<String> = [];

override public function create()
{
super.create();

// prev shit copied from latencystate
prevPersistentDraw = FlxG.state.persistentDraw;
prevPersistentUpdate = FlxG.state.persistentUpdate;

Expand All @@ -40,71 +49,193 @@ class ModsSelectState extends UISubState

Cursor.show();
WindowManager.instance.reset();
WindowManager.instance.container = windowContainer;

for (mod in PolymodHandler.getAllMods())
changeableModList = Save.instance.enabledModIds.copy();
reloadModOrder();

modListLoadAll.onClick = function(_) {
changeableModList = PolymodHandler.getAllModIds().copy();
reloadModOrder();
}

modListUnloadAll.onClick = function(_) {
changeableModList = [];
reloadModOrder();
}

modListLoadedBox.registerEvent(UIEvent.COMPONENT_ADDED, function(_) {
modListApplyButton.disabled = false;
});
modListUnloadedBox.registerEvent(UIEvent.COMPONENT_ADDED, function(_) {
modListApplyButton.disabled = false;
});

modListApplyButton.onClick = function(_) save();
modListExitButton.onClick = function(_) close();
}

// This should also account for the mod order and put dependencies on the bottom.
function reloadModOrder()
{
modListUnloadedBox.removeAllComponents();
modListLoadedBox.removeAllComponents();

var allMods:Array<ModMetadata> = listAllModsOrdered();

for (mod in allMods)
{
var loaded = Save.instance.enabledModIds.contains(mod.id);
var isLoaded:Bool = changeableModList.contains(mod.id);
var dependableChildren:Array<String> = [];
var optionalChildren:Array<String> = [];

var button = new ModButton(mod);
for (childMod in allMods)
{
if (childMod.id == mod.id) continue;
for (dep => ver in childMod.dependencies)
{
if (dep == mod.id && ver.isSatisfiedBy(mod.modVersion) && !dependableChildren.contains(mod.id)) dependableChildren.push(mod.id);
}
for (dep => ver in childMod.optionalDependencies)
{
if (dep == mod.id && ver.isSatisfiedBy(mod.modVersion) && !optionalChildren.contains(mod.id)) optionalChildren.push(mod.id);
}
}

// Update color of the mod title based on if it's an optional dependency, a required dependency or not a dependency at all.
var overrideColor:Null<String> = null;
if (optionalChildren.length > 0 && dependableChildren.length == 0)
{
overrideColor = "0xffff00";
}
else if (dependableChildren.length > 0)
{
overrideColor = "0xff8c00";
}

var button = new ModButton(mod, overrideColor);
button.tooltip = "Click to Enable/Disable.\nRight Click to View Info.";
if (isLoaded) button.tooltip += "\nShift+Click to Move Upwards.\nCtrl+Click to Move Downwards.";

// Check if there is a window present and apply a different style to the corresponding button.
if (windowContainer.childComponents.length > 0)
{
var firstComp = windowContainer.childComponents[0];
if (Std.isOfType(firstComp, ModInfoWindow))
{
var modWindow:ModInfoWindow = cast firstComp;
if (button.linkedMod.id == modWindow.linkedMod.id
&& button.linkedMod.modVersion == modWindow.linkedMod.modVersion) button.styleNames = "modBoxSelected";
}
}

button.onRightClick = function(_) {
for (window in WindowManager.instance.windows)
WindowManager.instance.closeWindow(window);
cleanupBeforeSwitch();
button.styleNames = "modBoxSelected";
var infoWindow = new ModInfoWindow(this, mod);

if (dependableChildren.length > 0) infoWindow.modWindowDependency.text = "This Mod is a Dependency of: " + dependableChildren.join(", ");
if (optionalChildren.length > 0) infoWindow.modWindowOptional.text = "This Mod is an Optional Dependency of: " + optionalChildren.join(", ");

var infoWindow = new ModInfoWindow(mod);
infoWindow.linkedButton = button;
WindowManager.instance.addWindow(infoWindow);
windowContainer.addComponent(infoWindow);
}

button.onClick = function(_) {
if (button.parentComponent == modListLoadedBox)
if (isLoaded)
{
modListLoadedBox.removeComponent(button, false);
modListUnloadedBox.addComponentAt(button, 0);
var modIndex:Int = changeableModList.indexOf(mod.id);
if (FlxG.keys.pressed.SHIFT) modIndex++;
else if (FlxG.keys.pressed.CONTROL) modIndex--;

var prevIndex:Int = changeableModList.indexOf(mod.id);
changeableModList.remove(mod.id);

if (prevIndex != modIndex)
{
// The priority of the mod has been changed.
modIndex = Std.int(flixel.math.FlxMath.bound(modIndex, 0, changeableModList.length - 1));
changeableModList.insert(modIndex, mod.id);
}
else
{
// Go through a list of all mods. If a mod depends on this mod to works and this mod's version satisfies the mod's version rule, remove it from the list.
for (childMod in allMods)
{
if (childMod.dependencies.exists(mod.id)
&& childMod.dependencies[mod.id].isSatisfiedBy(mod.modVersion)
&& changeableModList.contains(childMod.id)) changeableModList.remove(childMod.id);
}
}
}
else
{
modListUnloadedBox.removeComponent(button, false);
modListLoadedBox.addComponentAt(button, 0);
changeableModList.push(mod.id);

// Go through a list of all mods. If a mod is a dependency of this mod and it's version satisfies this mod's version rule, add it to the list.
for (childMod in allMods)
{
if (mod.dependencies.exists(childMod.id)
&& mod.dependencies[childMod.id].isSatisfiedBy(childMod.modVersion)
&& !changeableModList.contains(childMod.id)) changeableModList.push(childMod.id);
}
}

reloadModOrder();
}

if (loaded) modListLoadedBox.addComponent(button);
if (isLoaded) modListLoadedBox.addComponent(button);
else
modListUnloadedBox.addComponent(button);
}
}

modListLoadAll.onClick = function(_) {
while (modListUnloadedBox.childComponents.length > 0)
{
var mod = modListUnloadedBox.childComponents[0];
/**
* Order the mods so that the enabled mods are first.
*/
function listAllModsOrdered()
{
var allMods:Array<ModMetadata> = PolymodHandler.getAllMods().copy();
var finishedList:Array<ModMetadata> = [];

modListUnloadedBox.removeComponent(mod, false);
modListLoadedBox.addComponent(mod);
for (modId in changeableModList)
{
for (mod in allMods)
{
if (mod.id == modId)
{
finishedList.push(mod);
allMods.remove(mod);
break;
}
}
}

modListUnloadAll.onClick = function(_) {
while (modListLoadedBox.childComponents.length > 0)
{
var mod = modListLoadedBox.childComponents[0];
// Order the enabled mods by dependencies.
finishedList = DependencyUtil.sortByDependencies(finishedList);

// Add the remainding mods.
finishedList = finishedList.concat(allMods);

// Reverse the list so that the first mods go down.
finishedList.reverse();

modListLoadedBox.removeComponent(mod, false);
modListUnloadedBox.addComponent(mod);
return finishedList;
}

function cleanupBeforeSwitch()
{
for (window in WindowManager.instance.windows)
WindowManager.instance.closeWindow(window);

for (button in modListUnloadedBox.childComponents.concat(modListLoadedBox.childComponents))
{
if (Std.isOfType(button, ModButton))
{
var realButton:ModButton = cast button;
realButton.styleNames = "modBox";
}
}

modListLoadedBox.registerEvent(UIEvent.COMPONENT_ADDED, function(_) {
modListApplyButton.disabled = false;
});
modListUnloadedBox.registerEvent(UIEvent.COMPONENT_ADDED, function(_) {
modListApplyButton.disabled = false;
});

modListApplyButton.onClick = function(_) save();
modListExitButton.onClick = function(_) close();
windowContainer.removeAllComponents();
}

override public function close()
Expand All @@ -121,16 +252,9 @@ class ModsSelectState extends UISubState

function save()
{
var loadMods = [];
for (child in modListLoadedBox.childComponents)
{
loadMods.push(child.id);
}

trace("Loading Mods: " + loadMods);

Save.instance.enabledModIds = loadMods;
trace("Loading Mods: " + changeableModList);

Save.instance.enabledModIds = changeableModList;
PolymodHandler.forceReloadAssets();
modListApplyButton.disabled = true;
}
Expand Down
6 changes: 5 additions & 1 deletion source/funkin/ui/debug/mods/components/ModButton.hx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import polymod.Polymod.ModMetadata;
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/mod-select/components/mod-button.xml"))
class ModButton extends HBox
{
override public function new(mod:ModMetadata)
public var linkedMod:ModMetadata;

override public function new(mod:ModMetadata, ?changeTextColor:String)
{
super();

this.id = mod.id;
linkedMod = mod;

modButtonLabel.text = mod.id + " (" + mod.modVersion + ")";
if (changeTextColor != null) modButtonLabel.styleString = 'color: $changeTextColor;';

var img = openfl.display.BitmapData.fromBytes(mod.icon);
if (img != null) modButtonIcon.resource = new flixel.FlxSprite().loadGraphic(img).frames.frames[0]; // hacky way but it works
Expand Down
62 changes: 0 additions & 62 deletions source/funkin/ui/debug/mods/components/ModContributorWindow.hx

This file was deleted.

Loading

0 comments on commit 5fb7b4a

Please sign in to comment.