Cyberpunk 2077
0 of 0

File information

Last updated

Original upload

Created by

breadswonders

Uploaded by

breadswonders

Virus scan

Safe to use

About this mod

This mod removes the attribute level cap for gaining skill XP. This means that you will continue to level skills that you have not spec'd into and receive the associated rewards/unlocks. Only have 3 points in Cool and don't particularly like being stealthy? No problem, just yeet some bodies into trash cans and unlock those sweet perk points!

Requirements
Permissions and credits
Changelogs
Donations
Overview:
I always found it a little bit annoying that I would end up losing out on gaining skill XP when a skill level had reached it's associated attribute level, so I searched for a mod to change this, and I eventually found one called "Skills Always Gain XP", however, that mod has since been taken down, so I made my own mod.

One of the major benefits of this mod is that you get to reap the rewards of passively leveling skills that you aren't spec'd into and aren't putting any real effort into using, which means you will unlock the associated skill progression rewards and many more perk points as you go.

For instance, with this mod enabled and starting a new game, the first thing you should do is put a single point into Cold Blood, then, whenever you kill an enemy within 10 seconds of another (i.e. you have a stack of Cold Blood), you will gain XP towards cold blood, which will give you a ton of awesome skill rewards as you play through.

Installation:
This mod requires redscript, so make sure that is installed first.
This mod is supports Vortex installation and I strongly recommend you use Vortex to install this mod.
For manual installation, download the mod and extract the zip, then copy it to the root of your Cyberpunk 2077 directory, when you're done, you should see a directory called "Skillful" containing a bunch of ".reds" files in your "Cyberpunk 2077\r6\scripts\" directory.

Details and Mod Compatibility:
This mod uses redscript to trick the leveling system into thinking that each of your skills are at their max level when it checks to see if you should be able to gain XP for them, and I have specifically written this mod with compatibility in mind. There are no hard-coded values here, so if another mod changes something like the min and max levels for a skill or the player level, this mod should work fine with them. Specifically, this mod wraps the following method:private final const func GetProficiencyMaxLevel(type: gamedataProficiencyType) -> Int32

This method was wrapped instead of replaced to hopefully allow for other mods to modify this method as well, without breaking compatibility with this one. Further, this mod also checks the result of the original method call to see if the value returned is within the expected range (other mods can change this range without it being a problem, so long as the value fits in it) before we change it, if not, we log an error message and pass the value along - the assumption being that either something has gone horribly wrong, or that another mod needs this value to function. The code for this mod is well documented, so any other devs that want to take a look at it should have a very easy time doing so.

I can confirm that this mod works with:

Methodology Details For Devs:
My methodology for this came from inspecting the code for the original Skills Always Gain XP mod, noting that it basically only returned a hard-coded 50 for player level and street cred, and 20 for anything else. Digging into the engine code, we find that the function I replaced looks like this:

private final const func GetProficiencyMaxLevel(type: gamedataProficiencyType) -> Int32 {
  let absoluteMaxLevel: Int32;
  let attributeInt: Int32;
  let attributeMaxLevel: Int32;
  let attributeRec: ref<Stat_Record>;
  let attributeType: gamedataStatType;
  let attributeValue: Int32;
  let colName: CName;
  let curveSetName: CName;
  let proficiencyRec: ref<Proficiency_Record>;
  let statDataSys: ref<StatsDataSystem> = GameInstance.GetStatsDataSystem(this.m_owner.GetGame());
  let pIndex: Int32 = this.GetProficiencyIndexByType(type);
  if pIndex < 0 {
    LogDM("GetProficiencyMaxLevel(): Proficiency of given type doesn\'t exist!! Return value equals -1 !!");
    return -1;
  };
  this.GetProficiencyExpCurveNames(type, curveSetName, colName);
  if Equals(curveSetName, n"") {
    LogDM("GetProficiencyMaxLevel: Curve name is empty ! return value equals -1 !");
    return -1;
  };
  proficiencyRec = RPGManager.GetProficiencyRecord(type);
  absoluteMaxLevel = proficiencyRec.MaxLevel();
  attributeRec = proficiencyRec.TiedAttribute();
  attributeInt = -1;
  if IsDefined(attributeRec) {
    attributeInt = Cast(EnumValueFromString("gamedataStatType", attributeRec.EnumName()));
  };
  if attributeInt >= 0 {
    attributeType = IntEnum(attributeInt);
    attributeValue = this.m_attributes[this.GetAttributeIndex(attributeType)].value;
    attributeMaxLevel = attributeValue;
    return Max(Min(absoluteMaxLevel, attributeMaxLevel), 1);
  };
  return absoluteMaxLevel;
}

Notice that there's quite a bit of error checking in there? This is something that the original mod did not account for at all, although, I think that these errors could only be caused by another mod breaking something, it seems like it's probably a good idea to make sure that they get propagated to the rest of the engine code.
A further problem I have noticed with a few other mods, is that they don't seem to account for all possible values of the gamedataProficiencyType enum:

enum gamedataProficiencyType {
  Assault = 0,
  Athletics = 1,
  Brawling = 2,
  ColdBlood = 3,
  CombatHacking = 4,
  Crafting = 5,
  Demolition = 6,
  Engineering = 7,
  Gunslinger = 8,
  Hacking = 9,
  Kenjutsu = 10,
  Level = 11,
  Stealth = 12,
  StreetCred = 13,
  Count = 14,
  Invalid = 15,
}

Count is used to tell you how many values exist in the enum for you to enumerate, this is used in several places to loop through the gamedataProficiencyType enum, so it would be VERY bad if you return anything but the actual and unmodified value of Count that the original function produces.
Invalid has a pretty self-explanatory name, but I have not been able to find it used in the engine code that I have seen, but that's actually a much better reason to make sure the regular engine code handles this value - because I have no idea what it does.