Yeah, those sound like serious issues. I don’t know how feasible it is to automagically back-port updates to customized Lua-ported features. I was going to originally suggest modularization, but even if we supported something like:
---------------------------
-- Chat Module --
---------------------------
local chatModule = {}
function chatModule:GetPlayerChatColor(player)
return self.chatColors[player.UserId % #self.chatColors]
end
return chatModule
----------------------------
-- User Script --
----------------------------
local chatModule = require(chatModule)
chatModule.GetPlayerChatColor = function(self, player)
return player:IsInGroup(1) and Color3.new(1,1,1) or Color3.new(0,0,0)
end)
to make minor customizations, what happens when we start changing the state differently in a replaced function and the feature breaks because another part of the script expects the state to be different? What happens when we want to change the arguments/what we return and a user has replaced the function?
I don’t think we’ll make major headway on the problem without picking a different angle. Is it really the best course of action to have developers tweaking the source code for minor features like font, or should we be providing settings so there’s no need to tweak the source? The following changes may resolve most issues we have with customizations to Lua-ported features:
Remove the need for minor tweaks as much as possible
If a developer wants to change the background color of chat bubbles, what incentive is there to do that by modifying sourcecode? Not all developers are programmers, and changing through source modification creates problems with future updates.
We could provide the most commonly-desired tweaks through settings, and monitor games that replace the chat/etc to see if any of those changes could be incorporated as settings. We might also consider making a thread on the developer forums asking for tweaks they’d like to or have already made towards these features and incorporating these as settings. These settings should ideally remove the need for minor tweaks to the source code.
Allow developers to provide custom callbacks
Not all behavior can be accomplished through settings. For instance, what if I want to change users’ default chat colors so that they’re based on their in-game rank? For stateless functions whose purpose is calculation, we can allow developers to provide custom callbacks similarly to the code example provide above. We can do unit tests on these to enforce proper output values so they’re compatible with future updates. For instance, GetPlayerChatColor should always return a single Color3 value.
Only stateless functions will ever provide callback functions like this. Changing them will not affect the rest of the feature so long as the output is correct, and if ROBLOX does make changes to it, it will be in the calculation – the output type will still be the same. If the Lua-ported feature is designed correctly, most developers will not need to tweak it outside of callbacks/settings.
Overwriting the feature anyway
If for some reason a developer still needs to tweak the Lua-ported feature, they can disable the ROBLOX feature and fork it into their game. They will not receive future updates to any of the components of the feature, so if they change the local script they opt out of changes for the server script and everything else. This could be implemented in a variety of ways, but the following may be a good start:
We’d create a replicating and explorer-visible service called “RobloxFeatures”/etc and its children would be instances of type RobloxFeature. RobloxFeature would have an Enabled/Disabled property and its children would be:
- Folder “SourceCode”
- Folder “Settings”
Children of the SourceCode folder would be the script components used by the Lua-ported feature (e.g. ChatServiceRunner and ChatScript). Scripts/LocalScripts that are children of a RobloxFeature run as soon as the game/player loads. Children of SourceCode are non-archivable. When a developer opens up a script/localscript inside a RobloxFeature, they are able to view the source. If they try to edit the script, they get a warning popup, similar to TC’s “Changes made in Play Solo will be lost”, that says “RobloxFeature scripts cannot be edited. Please disable the RobloxFeature and copy the scripts into your game.”
Children of the Settings folder would be ValueObjects like BoolValue/Color3Value. Their name would be a user-friendly descriptor of the setting, and the value would be how the setting is configured. These settings instances are archivable. When a new setting is implemented, it will be created in this folder. These settings are Value instances instead of a ModuleScript because with a ModuleScript we don’t have the ability to add settings to it each update.
This provides the following benefits:
- Features are all in one place
- Disabling features is elegant
- Is easy
- Not hacky (creating blank scripts)
- Does not create property bloat in various services
- No half-updates
- To refactor a localscript, you have to disable the entire feature, and would no longer get updates for the server script either which could cause the game to break
- Since (local)scripts in a RobloxFeature run, there’s no need to parent them anywhere else
- Doesn’t break developer scripts that expect the game hierarchy to be a certain way
- Doesn’t need to spread them across the game (e.g. CameraScript in StarterPlayerScripts and DefaultChatSystemChatEvents in ReplicatedStorage)
- We can show feature source code in Edit mode since it’s non-archivable
- Intuitive, so developers can figure out how to get source code without help from a wiki article they may/may not read
- We can interactively teach the developer how to customize the feature
- If they want to edit a feature, their natural reaction is to edit the script. When they do that, we tell them how to customize instead of hoping they read a Wiki article
- We can provide settings intuitively without the user having to go to the Wiki
We may want to remove settings in the future, in which case we can add a “Version” property to RobloxFeature. When we update a feature, this increments if there are no conflicts. If there is a conflict such as a setting being removed, the feature will remain at its current version until the developer manually resolves the issue (should not be too annoying since this kind of update will be rare, and deleting an instance in settings is no biggie). We would have to notify developers of conflicts somehow.
To provide backwards compatibility, RobloxServices would be built both at runtime (for games that haven’t been published to since the latest feature change) and in Studio. If it detects a custom script (e.g. CameraScript in StarterPlayerScripts), it will automatically disable the feature when it is created.