Conflicts are never enjoyable - especially in modding. They can frustrate both mod creators and users alike. This is particularly true for UI mods, which require their files to be overwritten.
No longer! Well... Sort of.
There is still the issue of updating the HTML files themselves, which I will be doing more digging into, but the biggest obstacle right now is overwriting JS files.
With this method, mods should be able to modify different functions in the same file without having to overwrite the whole file (and another mod's work).
I've been digging into solving this problem, and I think this should work for most people's use cases. The answer is in events, and prototypes.
I won't bore you with the details - If you're interested in learning more about promises and prototypes, check out:
Let's set up a minimal project:
You can see that I've created a minimal `no-conflict-test.modinfo` file - All I have is a single item declared that sits in the same folder. Nothing fancy. Let's add a simple js file.
And that's all you need to get code executing without custom panels or otherwise. Let's add a small test to make sure it is working:
Now when you open the debugger, in the console you should see the test message show up every two seconds.
Now let's make it do something:
All I do is import the panel I want to modify, overwrite the function I want to modify on the Panel's prototype, and done! In this case, I remove the URL of the 2K icon so that it doesn't show up in the upper right corner in what's called the System Bar.
I've attached the code here so you can open it and play with it yourself.
Threw this together pretty quickly, but I felt it was important to get out there. Let me know if you run into issues or have any questions.
---
Update: 2025/2/16
As @RealityMeltdown mentions below, it is often a good idea to run the original function, then add your code that runs after (where you can, this cannot always be done). If you do this, multiple people using this same 'additive' approach would further reduce conflicts!
Not only that, but you can often simplify your change and only include the slice of what you need. Continuing with our code above, this approach would look like:
Now, if two people touch the same function, there is reduced risk of them stepping on each other's toes! The result:
No longer! Well... Sort of.
There is still the issue of updating the HTML files themselves, which I will be doing more digging into, but the biggest obstacle right now is overwriting JS files.
With this method, mods should be able to modify different functions in the same file without having to overwrite the whole file (and another mod's work).
I've been digging into solving this problem, and I think this should work for most people's use cases. The answer is in events, and prototypes.
I won't bore you with the details - If you're interested in learning more about promises and prototypes, check out:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
- https://developer.mozilla.org/en-US...Advanced_JavaScript_objects/Object_prototypes
Let's set up a minimal project:
XML:
<?xml version="1.0" encoding="utf-8"?>
<Mod id="no_conflict_test" version="1"
xmlns="ModInfo">
<Properties>
<Name>No Conflict Test</Name>
<Description>Testing a method of adding UI changes without conflicts.</Description>
<Authors>Conexion</Authors>
<Package>Mod</Package>
<AffectsSavedGames>0</AffectsSavedGames>
</Properties>
<ActionCriteria>
<Criteria id="always">
<AlwaysMet></AlwaysMet>
</Criteria>
</ActionCriteria>
<ActionGroups>
<ActionGroup id="game-no-conflict-test-always" scope="game" criteria="always">
<Actions>
<UIScripts>
<Item>no-conflict-test.js</Item>
</UIScripts>
</Actions>
</ActionGroup>
</ActionGroups>
</Mod>
You can see that I've created a minimal `no-conflict-test.modinfo` file - All I have is a single item declared that sits in the same folder. Nothing fancy. Let's add a simple js file.
JavaScript:
const yourModInitFunction = () => {
// Your code here
}
// Add your function to the engine's ready event
engine.whenReady.then(yourModInitFunction);
And that's all you need to get code executing without custom panels or otherwise. Let's add a small test to make sure it is working:
JavaScript:
const yourModInitFunction = () => {
setInterval(() => {
console.error("<----<< TEST >>---->");
}, 2000);
}
// Add your function to the engine's ready event
engine.whenReady.then(yourModInitFunction);
Now when you open the debugger, in the console you should see the test message show up every two seconds.
Now let's make it do something:
JavaScript:
// Panel you want to modify
import { PanelSystemBar } from '/base-standard/ui/system-bar/panel-system-bar.js';
const yourModInitFunction = () => {
// Overwrite the onMetagamingStatusChanged method to remove 2K icon
PanelSystemBar.prototype.onMetagamingStatusChanged = () => {
if (!Network.supportsSSO()) return;
const container = document.getElementById("ps-icons");
if (container) {
const newConnectionButton = document.createElement('fxs-activatable');
newConnectionButton.setAttribute('caption', Locale.compose("LOC_UI_METAPROGRESSION"));
newConnectionButton.setAttribute("radial-tag", "ps-bar-metaprogression");
newConnectionButton.id = "metaprogression";
newConnectionButton.classList.add("ml-3", "size-4", "bg-cover", "mt-1");
if (Network.isMetagamingAvailable()) {
newConnectionButton.style.backgroundImage = "url('')"; // Remove 2K icon
newConnectionButton.setAttribute('data-tooltip-content', Locale.compose("LOC_UI_ENABLED_METAPROGRESSION"));
}
else {
newConnectionButton.style.backgroundImage = "url('')"; // Remove 2K icon
newConnectionButton.setAttribute('data-tooltip-content', Locale.compose("LOC_UI_DISABLED_METAPROGRESSION"));
}
const oldConnectionButton = document.getElementById("metaprogression");
if (oldConnectionButton) {
container.replaceChild(newConnectionButton, oldConnectionButton);
}
else {
container.appendChild(newConnectionButton);
}
}
}
}
// Add your function to the engine's ready event
engine.whenReady.then(yourModInitFunction);
All I do is import the panel I want to modify, overwrite the function I want to modify on the Panel's prototype, and done! In this case, I remove the URL of the 2K icon so that it doesn't show up in the upper right corner in what's called the System Bar.
I've attached the code here so you can open it and play with it yourself.
Threw this together pretty quickly, but I felt it was important to get out there. Let me know if you run into issues or have any questions.
---
Update: 2025/2/16
As @RealityMeltdown mentions below, it is often a good idea to run the original function, then add your code that runs after (where you can, this cannot always be done). If you do this, multiple people using this same 'additive' approach would further reduce conflicts!
Not only that, but you can often simplify your change and only include the slice of what you need. Continuing with our code above, this approach would look like:
JavaScript:
// Panel you want to modify
import { PanelSystemBar } from '/base-standard/ui/system-bar/panel-system-bar.js';
const yourModInitFunction = () => {
// Save a reference to the original function
const prevFunction = PanelSystemBar.prototype.onMetagamingStatusChanged;
PanelSystemBar.prototype.onMetagamingStatusChanged = function (...args) {
// Call the original function
prevFunction.apply(this, args);
// Make your changes here
const button = document.getElementById("metaprogression");
if (button) button.style.backgroundImage = "url('')"; // Remove 2K icon
};
};
// Add your function to the engine's ready event
engine.whenReady.then(yourModInitFunction);
Now, if two people touch the same function, there is reduced risk of them stepping on each other's toes! The result:
Attachments
Last edited: