The Humanoid's 80/20 features should be ported to Lua

User Stories:

As a developer, I am forced to make custom characters that don’t use Humanoids to get rid of unwanted Humanoid behavior
As a developer, I am unable to emulate Humanoid behavior easily or cleanly (e.g. making a multi-jump is either hacky (setting Sit/Jump repeatedly) or I have to guess how the Humanoid does it)

Use Cases:

A Solution:

80% of time is spent running 20% of code, and 20% of time is spent running the other 80% of the code: the 80/20 rule(guideline) applied to software development. It’s not exact, but usually there are specific features that use up the majority of resources (e.g. physics) and are constantly being calculated, and then there are a lot of other features that aren’t constantly being used (e.g. breaking joints on death).

It’d be great if the 80% of Humanoid implementation was moved over to Lua. Right now, Humanoids notoriously work via black magic and are continually abandoned so developers can make custom humanoids they have control over. Not only does this waste development time for us as developers, but it leads to a less pleasant experience for players because custom Humanoids aren’t as tried and tested as the real thing. Moving the 80% to Lua allows developers to tweak Humanoids to suit their needs without resorting to hacks or custom Humanoids. Since we’re only moving the 80% over, there should be no performance loss, as the most performance-intensive features are still running on the C side.

110 Likes

This would be so helpful to edit, I have a really hacky walkaround setup for this and even then what I have currently seems low quality.

7 Likes

I agree, however there are some issues with this that are pretty difficult to solve and re-writing all the Humanoid logic is difficult enough in itself.

The first problem I see is how these scripts would be distributed. These can’t be distributed in the same way as StarterCharacter/StarterPlayer scripts because there are non-player Humanoids and we still need to support these.

Making these scripts children of the Humanoid object that are inserted automatically from something like StarterHumanoidScripts when the game is loading and whenever a Humanoid is created at run-time might be a solution but it seems kind of in-elegant and inserting new scripts into games always has the possibility to break stuff.

There is the secondary problem of making the Humanoid logic easy to edit without doing a complete fork of all the Humanoid code. Due to how often the Humanoid code currently changes I think it could cause problems when Roblox wants to update something if a lot of developers have completely forked the Humanoid code. This might be able to be solved with an intricate system of settings and behavior modules that make complete forking less necessary. Another option is that Roblox could provide a tool for diffing and merging updated scripts in Studio.

I definitely think this is worth doing, just wanted to bring up some things that I think are the major obstacles that need to be overcome to make this work.

6 Likes

Yep – definitely a problem. This is also an issue with other functionality ROBLOX has ported to Lua (camera/control scripts, chat, chat bubble, Animate, etc). We need a solution for all of them in general. Although not ideal, I wouldn’t think this would be a blocker since we haven’t had any critical issues with the existing Lua scripts AFAIK. When we find a solution, we can put the Humanoid code through the same process as the camera/control scripts and friends.

Also a problem. Instead of using scripts/localscripts per each Humanoid, we could create a service script/module that handles all Humanoids. Any API methods ported to Lua (e.g. EquipTool) would just redirect to Lua service script like SetCore does in the ROBLOX corescripts, and part of Humanoid’s init code could be to register itself with the Lua service script. If we really want scripts/localscripts per each Humanoid, we can have the Humanoid’s C init code clone from StarterHumanoidScripts or wherever which may be preferable to subscribing to DescendantAdded.

We can avoid this entirely by using a service script rather than creating a script for each Humanoid, but otherwise, again, this seems more in general than specific to Humanoids.

1 Like

I don’t think it is a blocker but it is definitely something that should be considered. The chat and the control/camera scripts have both had issues with backwards compatibility. For the chat, we made a small change to the way setup events were ordered on the server and this broke a few high profile games that had forked just the client side code.

We did not move all the camera code into Lua, and now it is not easy to see how all the camera code can be moved to Lua because it would break games that have forked the camera scripts. We also have a similar problem with the desire to move click to move from camera to control scripts. Another issue we faced was when we made keyboard movement work when connected to tablets this was not fixed in games with old versions of the control scripts.

I think similar issues would be faced with moving the humanoid code to Lua, particularly as it is likely only a portion of it would be moved to Lua. There are also some updates to humanoids that could be made in the future that may be important for games to include. If a similar percentage of top games override humanoid script as camera/control scripts then these changes might be harder to make due to interactions between old Lua code and updated C code.

1 Like

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.

13 Likes

Treat the humanoid as a script/corescript, store the code inside the actual humanoid object.
If the humanoid differs from the roblox-written code, then still classify it as a humanoid but give it a seperate icon like a small gear from the configuration icon.
That way you can still have NPCs that follow Roblox’s main code and players that can differ or change unique NPCs code. I’m unfamiliar with how this could be achieved, but we have a system in place where the Camera and Controller scripts do not appear if there is already an object of the same name in the PlayerScripts folder, how much would something like this take?

I give my support, this is a large pain point and there’s no better time to address it than now. It needs to be taken care of before the new characters displayed @ RDC are implemented.

1 Like

I support this, although the implementation details would require some thought. I’ve had to struggle with a lot of bizarre Humanoid nuances that ended up requiring strange hacky workarounds, or rewrites of core functionality.

The latest one I’ve been thinking about is characters not turning to face the direction they’re moving when they’re too heavy. If something like a BodyGyro or some setting was exposed then this could be as simple as increasing a maximum torque value.

1 Like

I support this - it would be cool to see what developers would do with this stuff. I’d probably make it be so players can’t move in the air after they jump. Or I’d change the dimensions of the player so it’s more realistic.

This is actually handled by the ControlScript which is already modifiable. For one of my projects, I changed it so in-air movement wasn’t impossible, but the factor of movement was reduced.

3 Likes

Just came across a thread in development support of someone having this issue, and is something I’ve been forced to work around far too many times in the past.

This one is so annoying and why did this ever look like a good idea. What’s being suggested here is the ability to modify it out as a developer, I know. But I don’t see why this should be the default behavior.

As Roblox has grown there’s always been things that got removed due to it not making sense for all games. A big example is hat dropping. Preventing hat dropping involved weird hackish methods before, whereas now it’s a standard feature we can implement in our game if we want it. If we wanted a player to die, we would do so through setting their health, ideally.

Sadly changing the behavior of how humanoids die would have a huge impact on games. The humanoid already has a lot of properties we have to use for configuration, so I don’t know if adding a property to control this is ideal.

Edit: It wouldn’t even need to be a property if configuration was added, actually. Just setting the state ‘Dead’ to disabled shouldn’t still make the humanoid die. This feels like something that should replicate to the client if done on server, but I don’t know how easy or possible that would be.

5 Likes

I think it would be pretty nice to introduce C scripts into roblox - 99% of the gaming industry uses C-based languages, so it would be good for people to learn it so when they outgrow roblox they can move on to another game engine. But then again, I guess roblox doesn’t really want to lose their fan base.

Why would a new language make them lose a fan base?

1 Like

It wouldn’t really effect it that much, but what I assume would happen is that once devs get used to C in roblox, they begin to realize the potential of other game engines, then start making things in game engines like Unreal or Unity as opposed to roblox. But I’m probably wrong so idk :stuck_out_tongue:

1 Like

Personally I’d love to see C++ or C# support in Roblox, would be awesome and helpful.

3 Likes

Support. From what I’ve heard, a lot of developers try to avoid using the Humanoid object unless they have to due to its limited features/inefficiencies.

8 Likes