• We have updated our Community Code of Conduct. Please read through the new rules for the forum that are an integral part of Paradox Interactive’s User Agreement.

blackninja9939

Senior Programmer - Crusader Kings 3
Paradox Staff
98 Badges
Aug 28, 2013
2.414
8.541
  • Surviving Mars: Digital Deluxe Edition
  • BATTLETECH: Heavy Metal
  • BATTLETECH: Season pass
  • Battle for Bosporus
  • Crusader Kings III: Royal Edition
  • Cities: Skylines - Snowfall
  • Hearts of Iron IV: No Step Back
  • Cities: Skylines - Parklife
  • BATTLETECH - Digital Deluxe Edition
  • Age of Wonders: Planetfall Season pass
  • Stellaris: Humanoids Species Pack
  • Empire of Sin
  • Cities: Skylines - Green Cities
  • Cities: Skylines - Mass Transit
  • Age of Wonders III
  • Stellaris: Necroids
  • Hearts of Iron IV: Death or Dishonor
  • Age of Wonders: Planetfall - Revelations
  • Cities: Skylines Deluxe Edition
  • Stellaris: Federations
  • BATTLETECH: Flashpoint
  • Europa Universalis IV
  • Europa Universalis 4: Emperor
  • Island Bound
  • Hearts of Iron IV: By Blood Alone
  • Crusader Kings III
  • Stellaris: Nemesis
  • Cities: Skylines Industries
  • Cities: Skylines - Campus
  • Crusader Kings II
  • Crusader Kings II: Holy Fury
  • Imperator: Rome - Magna Graecia
  • Crusader Kings II: Charlemagne
  • Crusader Kings II: Rajas of India
  • Crusader Kings II: Sons of Abraham
  • Crusader Kings II: The Old Gods
  • Europa Universalis IV: Rights of Man
  • Europa Universalis IV: Cradle of Civilization
  • Stellaris: Synthetic Dawn
  • Surviving Mars
  • BATTLETECH
  • Europa Universalis IV: Mandate of Heaven
  • Crusader Kings II: Monks and Mystics
  • Tyranny: Archon Edition
  • Europa Universalis IV: Rule Britannia
  • Crusader Kings II: Reapers Due
  • Hearts of Iron IV: Colonel
  • Stellaris Sign-up
  • Hearts of Iron IV: Expansion Pass
  • Stellaris: Apocalypse
Hello everyone and welcome back to Anatomy of a Game. I’m Matthew, a programmer on Crusader Kings 3. Today I am going to be talking about the Script System and how it works to let our Content Designers and modding community create their wonderful events and mechanics.

Firstly I am going to discuss the reason we even have this system and its soft goals for usage and how we grow it over time.
Then I will go into the implementation behind it and how we use it both from the game team side and the more foundational library side of it in Jomini which is our shared grand strategy library we use alongside our Clausewitz engine.

The why​

So why do we have this, you saw me last week complaining about the performance of it so gotta be a good reason we have it right?

It is much quicker to change things in script and makes those changes easier to maintain in a controlled environment. It also is generally easier to use a controlled scripting language than make everyone learn how to code in C++. We’ve got a wide set of roles on the team and forcing all of them to also be programmers to do small changes would be crazy,
So we want some reasonably straightforward way for people to add new things without needing to know C++ and edit and recompile the game. This is very common across most games and engines.

Our key goals with the the scripting language is:
  1. Help our Content Designers do their job
  2. Straightforward plain english with minimal complex syntax
  3. Easy to extend with new content and features over time
  4. No foot guns, one should not be able to cause catastrophic issues from the script

Really everything is just a subset of goal 1 there but it's good to list some more concrete things. For our scripting language specifically it's sort of evolved over time, it bears some superficial similarities to JSON but predates it by a few years and is less formally structured, which can be a blessing and a curse.
We have full control of it but it's also unique to us so nobody comes pre-trained in it and how we operate with it nor is it formally structured enough that there isn’t a delightful set of edge cases. One of our Content Designers likes to describe it a bit like the American legal system, a whole lot of case law and precedents set as opposed to a more well codified instruction set.

And you’ll also notice that I’ve not mentioned modders in those bullet points, and the reason for that is whilst we do want our modding community to use this and we regularly add things for them specifically it is never to be things that come at the cost of the key goals. I’ve received a lot of mod requests for things we could add but would complicate the language to a large extent making it harder for our Content Designers and coders to reason about its setup. That isn’t to say just because it isn’t used in vanilla means we shouldn’t add it, but we’ve got to keep our key goals in mind that this language exists for a specific job not to be some generic game making tool as there are other pieces of software dedicated to that task.

So with that out of the way let's jump into some more details about our scripting language internals specifically.

What is a scope?​

A scope is pretty simply just some game object such as a character, province, or faith. Each scope object is basically an instance of two values: scope type and identifier. The scope type is some number that represents if it is a character or province and then combined with the identifier we can then work out which specific object is represented by the scope.
So a scope object with type 5 and id 50 might be the King of England, whereas a scope with type 6 and id 50 could be Paris.

When do we make something a scope type then? That is also pretty simple, it needs to fit the following rules generally:
  • Read data from it (evaluate triggers)
  • Write data to it (execute effects)
  • Move from one object to another (links and lists)

If it doesn’t meet at least two (preferably all 3) of these requirements then making it a scope type is an overhead and burden we would prefer not to do so.

Implementing something as a scope type is mostly tying it into a few places, we have conversion functions to go back and forth between the abstract scope object and a real game object, this is where we tell it that type 5 means character and how to use id 50 to find a character. For most of our gameplay objects they use a system where each object has an id, so we must look up that object at that id and get the real game object.

Now there are some exceptions to this rule, namely what we call the primitive scope types. That is things like boolean values (yes/no), numbers (4/2.5), and flags (flag:cool_flag). They are special in that they do not meet any of the previous requirements and are instead simply the piece of raw data themselves as they are so simple, so their ID is actually a raw representation of their information.
Why are these primitives even scopes? Mainly to make things easier, it means when we want to store a scripted variable on a character we can use the exact same mechanism for storing and comparing some number as we do for storing their favourite teddy bear’s name as we do for storing the character they owe money to in some event.

1628166095643.png

Changing scopes​

There are two ways of changing scopes, a 1-1 link and a 1-many list. An easy example being liege-vassal relations as a character has one direct liege but can have multiple vassals to whom they are the sole direct liege.

For 1-1 changes you can chain multiple of these links together for example to get to your father’s father (your paternal grandfather) in one jump. In our backend near everything taking some object from script lets you do these multiple chaining of links so you don’t need to have cursed things like CK2’s big prevprevprev chains or constructs like root_fromfrom if any of you modders remember that bit of suffering.

1628166152858.png


You can also save a reference to a scope object with a given name to then use later in an unbroken context (more on these contexts later) which lets you save a more complex chain of links into one simpler named thing for a direct lookup. We do this liberally in vanilla since often a named thing is easier to understand than a long chain of links, this is also what we use when we set up the things involved in an on action so that scope:actor and scope:recipient are specific things with a name and meaning instead of just pushing everything into root, from, and fromfrom etc like CK2 did.

Lists operate in a similar fashion, you use some list name in a trigger or effect and it will iterate on all things meeting the criteria. In our backend we only register a list builder which then generates the any_xxx, every_xxx, random_xxx and ordered_xxx versions of it so that every list can be used in the same manner everywhere which removes the issues our older games sometimes had of missing one of those types despite the logic in theory being the same everywhere.

1628166279468.png

Triggers and Effects​

Onto the meat of the script system: our triggers and effects.

These really are the core of it as they give you a way to check and modify different objects, in code they are fairly simple to register as they mostly just map to calling a specific function on the object type as we aim to avoid doing complex logic directly in the triggers and effects so they don’t diverge from the logic we do in code (though there are some exceptions of course). The rest of the code for them is a standard bit boiler plate to register them correctly.

1628166328322.png


Implementation wise they use dynamic polymorphism, which is just a fancy way of saying one type can dispatch to do different things. Jomini only knows it's storing a collection of triggers, only at run time when it tries to evaluate one does it then figure out it's specifically an is_alive trigger.

Registering them is all well and good but we need to use them. We read in triggers and effects as part of our database objects which will just try to match what you typed to the list of registered things, if it cannot find the specifically named thing then it tries to see if it's a dynamic thing.
To make my paternal grandfather example from before, we do not register a specific trigger called “father.father” we just see that nothing matches that so then one of our dynamic fallbacks is seeing does this text fit the pattern to be a scope change which it does so make an instance of that trigger with the broken up scope change to traverse.
If it had not met that pattern then we would see if it's a scripted trigger and make an instance of the scripted trigger type with a reference to that specifically named one.

Once we’ve read them in then we're done with the actual text files and just use what we’ve got now in memory. From there it is up to the code to decide when to actually use this data.

We build some context for the usage, called a top scope, which contains the root object, a random seed, and optionally a group of saved scope references as I spoke of before.
Then pass that context into the trigger and effect, for triggers we get a return boolean of if the evaluation was true or not and for effects we just execute their contents directly.
This ties into what Meneth mentioned in the “Changing the Gamestate” post, triggers are just reading information so we can do these in parallel very easily with other triggers whereas effects must be executed serially as they can touch all manner of information throughout the whole game.

1628166415394.png


Another aspect of these is the visual side in tooltips, we need to tell you what is going to happen or why you cannot press that button after all. At a high level it works the same as running them for execution, we pass in some context and get some output.

For effects it is simplest, we pass in the context and if it's something that has already happened and then we build up the text description of what will or has happened based on the context. So which character that decision will kill or how much gold you paid for something in some notification.

Triggers are more complex to build up as we have to keep track of the logic inversions and groupings of going through all these nots, and ors as well as recording which things you passed or did not pass to show in different ways as most of the time we just want to show you what you are missing but sometimes we want to show you the full breakdown of the trigger including things you do fulfill.

With both of them though they undergo a simplification process to try and make the breakdown in text look nicer, we skip redundant information such as entirely hidden contents and try to condense down repeated actions to one object into a group. As we want to show things happening to the player in the first person: “I will lose 50 gold” and “I need at least 50 piety” Whereas for other characters we want to group repeated actions so instead of saying:
King John will lose 50 gold
King John will lose 100 piety
King John will lose 100 prestige

We can show combined:
King John:
Will lose 50 gold
Will lose 100 piety
Will lose 100 prestige

So that we reduce redundant information and keep related things together.

Events and On Actions​

Events and on actions are where a large amount of triggers and effects get used, they make up the foundation of content and narrative in CK3 weaving together little stories into your overarching narrative of each character and ruler.

The events themselves are from the code side rather straightforward, it is what one does inside of them in script that brings complexity, as all we do is read in the various information like requirements and text and the options and give them a pretty display in game.

Unlike in CK2 events do not trigger themselves by being regularly polled to see if they can, it was a performance drain, interruptive and made dispersing content nicely difficult so it has long since been removed and instead we use on actions for everything so that most content feels more like it comes from set actions or scenario you are in and paced a bit better.

On actions are one of the main ways we communicate from code to the script, we notify that something has happened such as the year ticking over or a character being born and then script can jump off from there to run content. An on action can contain various groupings of events and other on actions to run to pick out what content is needed.
From the code side adding a new on action is incredibly easy, we just register it with a name and then call for that on action in the appropriate place with a top scope containing the relevant information passed in.

1628166458189.png

1628166568359.png

Modifiers​

Key values we need to check are a big part of the game, we use modifiers as the system for this so we can get out things like a character's diplomacy or advantage in combat from multiple sources in one place.

At the start of July I overhauled the internals of our modifier system to be better divided which will make explaining their parts a bit easier as the word “modifier” is one of the most overloaded words in our script system (the other being flag). But hopefully I can explain our divide a bit better now!

In code we register what is called a modifier definition which contains some constant identifier (we use an enum since it's mostly a finite set) and then a token that is used in the script side to link to that modifier definition.
This definition contains information about how we treat and display values for it, such as if it's a boolean or not and how many decimal places it should show or if it should be considered good or bad to have more of this value. Previously we did this formatting in code but as part of the rework it is exposed to script now so that adding or removing a decimal place now doesn’t need code time.

1628166596759.png


Everything that the script deals with is called a modifier instance which is a group of pairs of definitions and values. So when you define the effects of having say the majesty focus selected to be adding diplomacy and monthly_prestige it is reading that into an instance.
We rarely operate on instances directly since getting values from them is often not particularly useful as they are static data, we do often build tooltips from them however so that we can list all the changes that they will cause from something like a focus or a trait.

Under the hood we store them as two sorted arrays of modifier definition and value. They are sorted such that the index into the definition array matches the index into the value array to get the value for that definition.
We do it like this instead of using something like a hashmap because we have a lot of modifier instances around in the game from a lot of sources and do not want the overhead of the hashmap’s bookkeeping. We have them sorted since then we can use the faster binary search instead of linear search to get data out of them to make up for not having them in a hashmap. Though after the rework that may change if the overhead looks to not be too bad any longer.

Modifier collections are what each character has that determines the values we get. It contains references to all the modifier instances that are its sources as well as a cached instance representing the total of all the applied modifiers.

The key invariants we have with a modifier collection is that every single instance added to it must have a stable memory address and a name we can display from. We require a stable address because we do not want to copy all this data around, most modifiers do have a stable location anyway as they come from some scripted database which is stable, for those that do not then we give them a stable address through other means such as in an array we do not resize or from an allocator that provides stable storage.

We rebuild this when we have to if some operation has marked it dirty, as rebuilding it is not the fastest thing as it's a lot of data we just flag modifiers as dirty and rebuild on the next day instead of having every operation rebuild it right away as generally it being a bit out of date is not a large concern.
There are some exceptions to this mainly centered around the player, as they will notice values about their character being out of date. So for the player we will flag it as dirty with a priority so that at the end of the next command posted we rebuild it which keeps the player up to date. Sometimes we will apply this prioritized behaviour to other characters if it's in a player interaction with them where they will notice it being wrong very quickly too.

When building tooltips for the collections we do not dump out the entire thing but we get the values for a specific modifier definition and list its sources. This is why we assert that every instance added must have a name set, otherwise if we later try to get it we’ll have no idea where it came from and what values it is meant to represent which can lead to some very useless tooltips.

1628166666220.png


And that concludes our quick tour of the script system! Thanks for reading!
 
  • 31Like
  • 15
  • 7Love
Reactions:
Previously we did this formatting in code but as part of the rework it is exposed to script now so that adding or removing a decimal place now doesn’t need code time.
So if I understand this correctly, any mod content generated modifier key (such as custom_terrain_advantage) will need something like $VALUE|0+=$ added to the localization in 1.5?

That's interesting, as then we could theoretically have conditional +- based on whether the player character considers - say - dread to be a good or bad thing to have.

Or does it mean that we'll now be able to decide whether a given modifier can take decimal values, such as skill modifiers which are currently locked to integers?
 
The scripting system is such a mixed bag. Some things seem perfectly reasonable, but other times you come across files that are just thousands of lines of copy and pasted if-statements trying to emulate a loop over a list or a dictionary lookup. I can't imagine the nightmare of trying to maintain that kind of code.
 
  • 7
  • 1
Reactions:
Thank you for this, I love these deep dives into how the systems work under the hood.

I hope we see some more of these in the future, they are incredibly interesting.
 
  • 2
  • 1Like
Reactions:
Why are many objects from script_docs and DumpDataTypes listed as unregistered or unknown?
For example, in data_types.log
Code:
Global Functions = {
    ⋮
    abandon_celibacy -> [unregistered]
    abandon_celibacy_decision -> [unregistered]
    abandon_hook_interaction -> [unregistered]
    abduct -> [unregistered]
    abduct_scheme -> [unregistered]
    abduct_schemes -> [unregistered]
    abduction -> [unregistered]
    absolute -> [unregistered]
    ⋮
}

or in event_target.log
Code:
--------------------
activity - Unknown, add something in code registration
Input Scopes: character
Output Scopes: activity
--------------------
betrothed - Unknown, add something in code registration
Input Scopes: character
Output Scopes: character
--------------------
capital_barony - Unknown, add something in code registration
Input Scopes: character
Output Scopes: landed title
--------------------
 
The scripting system is such a mixed bag. Some things seem perfectly reasonable, but other times you come across files that are just thousands of lines of copy and pasted if-statements trying to emulate a loop over a list or a dictionary lookup. I can't imagine the nightmare of trying to maintain that kind of code.
Agreed 100%.

@blackninja9939
One big question I have is why after so long is there still no support for strings in the scripting language. While I can understand why they might not have been very useful early on, with the scale of the mods being done these days I'm sure others have longed for the ability to manipulate and use strings in their code.
 
  • 1
Reactions:
Why are many objects from script_docs and DumpDataTypes listed as unregistered or unknown?
For example, in data_types.log
For the data types output its probably an edge case with how those functions are registered, for the link ones it was us only adding an explanation section to that registration a long time after we had added most of the links Though I believe for 1.5 we went through them all as tech debt and gave them all documentation there.
One big question I have is why after so long is there still no support for strings in the scripting language. While I can understand why they might not have been very useful early on, with the scale of the mods being done these days I'm sure others have longed for the ability to manipulate and use strings in their code.
What would it need strings for? Near ever "string" usage I've seen mods try to use is them hacking around something instead of making a legitimate request to support something.

We have flags which can be used as some constant string marker and then localized if needed, but dynamic string building would be a recipe for even more weird hacks or out of syncs with things being built and localized in different languages.
 
  • 3
  • 1
Reactions:
Hello! Loved this post! What color theme package are you using here (if it's for Microsoft Visual Studio)?
Visual Studio's dark mode with Visual Assist. I'm not sure if Matt has any modifications beyond that.
 
  • 4
Reactions:
The scripting system is such a mixed bag. Some things seem perfectly reasonable, but other times you come across files that are just thousands of lines of copy and pasted if-statements trying to emulate a loop over a list or a dictionary lookup. I can't imagine the nightmare of trying to maintain that kind of code.

I agree with that, and I'd be very interested to get insights on:

- the review process for both code and script

When new script is produced, how much (if any) of it is reviewed by programmers? Or is the script review only handled by the content designers themselves?

- the bug fix process, from the reports from QA/the forums, to the actual bugfix

How many of the bugs are from code, how many are from script? Who determines that, and who gets to fix those?

- the refactor process

You've talked about your modifier system overhaul, but who gets to decide what's worth refactoring, and what isn't?
 
  • 1
Reactions:
Thanks for explanation. Would you please write more about context switch? Is it based on recursion which start with the Top Scope, or there is an Array or List or Stack of involved scopes? And what "Link Class" really did? I assume it's just a static method holder which return a scope by inputs and careless about what context is running, due to screenshots about the source code?
Thanks again for this essay, it help a lot.;)
 
And by the way, what exactly the whole life time of a context scope about trigger/effect?
Is it created when the trigger/effect needs to implement and died when it's done? Or created earlier and died later?
And when it comes to mix of trigger/effect(such as "limit" of "if" or "random_xxx"), is it a "sub top context" to deal trigger via effect or just handled by the original top scope?
Is the process of tooltip building and real execution the same instance of top scope? Or just two cases, which the top scope died when tooltip showned, and when player hit the option, a new top scope will be created to handle the real execution? I guess the latter?
 
  • 1
Reactions:
What would it need strings for? Near ever "string" usage I've seen mods try to use is them hacking around something instead of making a legitimate request to support something.

We have flags which can be used as some constant string marker and then localized if needed, but dynamic string building would be a recipe for even more weird hacks or out of syncs with things being built and localized in different languages.
Considering how long even some mainstream issues take to get addressed I certainly can't blame anyone who attempts to mod around those issues in the meantime. As an example, right now I'd like to fix/mod the sorting of character lists by age to actually use the number of days old they are instead of just using the number of
years, but of course that value isn't available, nor are we as users able to convert the birth date string into a number either. I've reported the inaccuracy of the sorting by age as a bug already, but I'm not holding my breath for it to get addressed either.

As another personal project, I'd use string methods to form custom name string or nickname strings to identify characters in a more meaningful way. In CK2 I had a script that I wrote that dynamically determined a numeric value based on the commander traits someone had, then ran that number through a massive 2000+ line set of if statements checking every value to assign a specific string, rather an just being able to concatenate 2-6 individual strings together based on their traits. The funniest thing is that I generated the if statements in about 10 lines of perl code.

Granted I can see why having string support would be low on the list of priorities for the current suite of games the engine supports. But that doesn't mean the rest of us aren't going to come up with ideas where it would be useful. :)
 
This is a really really cool diary; I love getting to see a peek into the backend of the game's workings. Super cool and very informative.
 
  • 2Like
Reactions:
This may be out of scope (hehe) for what this DD is about, but as someone who's just learning to programme seeing how other people set up their code, and naming conventions is interesting to me, so if it's okay to ask can you explain why most things (I think they're variables? The functions and methods don't seem to have any prefix conventions) have a prefix letter?

CCharacter
CActivity
EScopeType
NJominiScope
SJominiScriptScopes

Etc, what do they mean and how does it translate to making it easier for you to work? I assume they stand for some kind of category?
 
I agree with that, and I'd be very interested to get insights on:

- the review process for both code and script

When new script is produced, how much (if any) of it is reviewed by programmers? Or is the script review only handled by the content designers themselves?

- the bug fix process, from the reports from QA/the forums, to the actual bugfix

How many of the bugs are from code, how many are from script? Who determines that, and who gets to fix those?

- the refactor process

You've talked about your modifier system overhaul, but who gets to decide what's worth refactoring, and what isn't?
Programmers do not review Content Designer's script unless its an especially technical bit we've had to add.

Bug fix process could be a whole post alone so gonna leave that. There are bugs from everyone, not sure of any real ratio, QA makes a ticket with a priority (not set in stone can be changed when looked at) and then we in our fix weeks churn through to fix them. We also do focused bug fixes sometimes, low hanging fruit day of trying to do as many smaller ones as one can to clean out the database.

Refactor decisions is mostly programmers and tech lead then approved by production and leads overall, so modifiers were a bit of a pain point so I came up with a rework with Meneth and made a tech debt task for it. Then we prioritised it with our tech lead in our tech debt time so when we got tech debt time over summer it was high up on the list of things to do.

Thanks for explanation. Would you please write more about context switch? Is it based on recursion which start with the Top Scope, or there is an Array or List or Stack of involved scopes? And what "Link Class" really did? I assume it's just a static method holder which return a scope by inputs and careless about what context is running, due to screenshots about the source code?
Thanks again for this essay, it help a lot.;)
So the link classes hold the read in data (if any eg scope:whatever will store the whatever) and then has a method that takes in some traversal context and uses that data if set and does the lookup it needs. Generally it doesn't care much about the context except what the current object is, some will look up into the top scope for temporary data but most just deal with the current object.

Execution and evaluation is sort of recursion in a sense? In that its all the same base classes calling repeatedly but its always on a new object looking for the most derived call not recrusion on the same objects. Each triggers is either a container of more triggers or an end point that does some specific evaluation, the evaluation of the container is to just evaluate its children and return that.

And by the way, what exactly the whole life time of a context scope about trigger/effect?
Is it created when the trigger/effect needs to implement and died when it's done? Or created earlier and died later?
And when it comes to mix of trigger/effect(such as "limit" of "if" or "random_xxx"), is it a "sub top context" to deal trigger via effect or just handled by the original top scope?
Is the process of tooltip building and real execution the same instance of top scope? Or just two cases, which the top scope died when tooltip showned, and when player hit the option, a new top scope will be created to handle the real execution? I guess the latter?
Generally it is very short life time, we make the context right before execution/evaluation and then once that is done it goes out of scope. For things like events it lasts as long as the event itself does and we just pass references/copies of it where needed.

Its the same context used in both triggers and effects in the nested ones because it needs to have access to all the same temporary data from the parent of the if and limit.

Tooltips are similar to the execution/evaluation, for a lot of things we just make it directly there at the call site (we usually have a shared function to make a top scope with the same data for some UI so that the tooltip, checks and clicks all have the same thing) but others like events tooltips will use a reference to the one stored in the event.

This may be out of scope (hehe) for what this DD is about, but as someone who's just learning to programme seeing how other people set up their code, and naming conventions is interesting to me, so if it's okay to ask can you explain why most things (I think they're variables? The functions and methods don't seem to have any prefix conventions) have a prefix letter?

CCharacter
CActivity
EScopeType
NJominiScope
SJominiScriptScopes

Etc, what do they mean and how does it translate to making it easier for you to work? I assume they stand for some kind of category?
C = Class
S = Struct
E = Enum
N = Namespace

Is just part of our studio code standards, originated as a sort of legacy from a time where we used "hungarian notation" more liberally where we'd also prefix the variables themselves too, personally I am not a fan of it and we've thankfully removed our hungarian notation for variables except p for raw pointers.

Some argue the notation makes it easier to tell what something is, personally I think that is generally clear from usage and giving your things good names (especially variables hence we removed it). For non-variables I sort of see the argument but again its clear if its a type or an enum or namespace from how you use it. But code standards are code standards so we do it!
 
  • 2
  • 1Like
Reactions:
Some argue the notation makes it easier to tell what something is, personally I think that is generally clear from usage and giving your things good names (especially variables hence we removed it). For non-variables I sort of see the argument but again its clear if its a type or an enum or namespace from how you use it. But code standards are code standards so we do it!
The one other advantage is that with the prefixes you can do:
const CCharacter Character = GetCharacter();
While without the prefix, it'd fail to compile due to a name conflict between the class and variable name (both "Character").
 
  • 1Like
Reactions: