Achievements - JulianR0/CLevels GitHub Wiki
An unmodified copy of this SCXPM contains 55 default achievements.
All achievements have an #ID assigned to it, which you can use to manually unlock (or lock) them.
Achievement Name | #ID |
---|---|
It would be a shame | 0 |
Who needs assistance? | 1 |
Bronce Chumtoad | 2 |
Silver Chumtoad | 3 |
Gold Chumtoad | 4 |
A secretless chumtoad | 5 |
Over MY dead body! | 6 |
Over YOUR dead body! | 7 |
Climbing all day | 8 |
Just another worker | 9 |
HD Graphics with 3D Support | 10 |
Svenmessa | 11 |
Electrician from Hell | 12 |
Gonome Degree | 13 |
NeoDifference | 14 |
No more fighting | 15 |
Express Squadron | 16 |
No Lifer? Lies! | 17 |
Routine tasks | 18 |
Apache? Where? | 19 |
S.A.C. Elite | 20 |
Cheating Death | 21 |
I can help, too! | 22 |
IMMORTAL | 23 |
2.5D Platformer | 24 |
My horse is amazing | 25 |
Decollapse | 26 |
Wasting time | 27 |
Upside down | 28 |
Unbeatable Defense | 29 |
A survivor from beyond | 30 |
Neutral Labyrinth | 31 |
Not so tedious, okay? | 32 |
quadrazid | 33 |
Forced to Discriminate | 34 |
Movie Ticket | 35 |
Suicide Squad | 36 |
Complete Infiltration | 37 |
Pure Atheism | 38 |
Return to sender | 39 |
Your face doesn’t scare me | 40 |
5 minutes equals 5 | 41 |
Never? Forever | 42 |
Nagoya | 43 |
Face vs Face | 44 |
Perfect Protection | 45 |
Expert Gausser | 46 |
Zero times Zero | 47 |
Like clockwork | 48 |
Yoshi’s Island | 49 |
Point of View | 50 |
That’s the question | 51 |
TAS-like | 52 |
Unbalanced | 53 |
Last Normalcy | 54 |
Is that it? | 55 |
Made in Quake | 56 |
Tedious luck | 57 |
So you want to spice up your server and create new and cool achievements? Here you will find all the information you need to create your very own custom achievement.
Before we begin, you need the following:
- Intermediate scripting skills.
- Knowledge of ripent tools.
- A bit of pacience.
It is not difficult to add new achievements once you get the gist of it, however getting there might be a little troublesome. Get some coffee if you need, as it may take two or more attempts to get it working.
The achievement system is a mess (to me, at least). I’ve once considered reworking it and let achievements be defined on an external file, but then I realized that was going to make things WAY worse so instead I left things as-is.
Okay, enough self-bashing. Let’s begin!
Open SCXPM.as with your favorite text editor and locate the szAchievementNames array. This array contains the names of all the achievements. Go to the end of the array and add a new entry to it, let’s name it “My awesome achievement”.
Now we need to add an objective to this new achievement. So we go to the GetAchievementMission() function, where now you will see a switch that handles all achievement’s objectives. Once again we go the end of the switch, and we add a new case just above the default case. In there we just write our awesome objective the players will need to do to unlock the achievement.
It is recommended that you add a comment containing the new achievement name as shown on the image above. That way you can keep track of achievement IDs. And don’t forget the break statement! You are inside of a switch, you wouldn’t want the wrong string getting displayed, do you?
That’s it!
You’ve just created a new achievement! Hunters will now go mad at the sight of a new achievement to complete.
What we have just done is the bare minimum of an achievement. Currently, the only way for players to get it is for an admin to manually unlock it. Ideally, you will want achievements to be unlockable on their own.
Let’s go back to our achievement’s objective and rewrite the text so players can have a clearer indication of what to do. Let’s take our good old svencoop1 map for this. Write that players need to open the very first door of the map.
So, using our new example, we need to trigger the achievement when the very first door is opened. To do this, we need to locate the targetname of the door entity.
Since this is a very simple example, we can directly get the door’s targetname with either the thatinfo or ent_name_ahead cheat command. Doing so will reveal that our targetname is firstdoor1. Save that name, as we will use it later on.
It’s time to open the SCXPM_AHandler.as file. And behold, it’s a mess!
The flow for a new achivement is to add the map in the MapActivate() function, then add some extra entities to the map and finally a Scheduler that will “Think” to check for achievement completition. Let’s go step by step.
Find the MapActivate() function. You will see a long hardcoded list of “if - else if” for all different maps. Go to the end of the list and add a new “else if” condition for our new svencoop1 achievement.
(NOTE: Outdated screenshot, replace g_SCTimer_1 with g_SCTimer)
Allow me to explain that mess you see on the above screenshot.
We first create an info_target which will act as a “relay”. If this entity is deleted, the game will no longer check the achievement. For consistency with the already existant achievements, the targetname of sys_adata is kept.
Next we create a trigger_changevalue, this will set a “flag” to the relay entity ($i_test). If you pay close attention to the changevalue’s targetname, it matches the door’s name -the one we need to open-. When the door is opened, the changevalue will trigger as well, since the targetnames are identical.
Lastly, the scheduler that will “think” for achievement completition. On this think, it will scan the relay entity (the info_target) and see if the flag is set (check if $i_test equals 1). The scheduler must repeat, otherwise the achievement will never be checked, and thus, players unable to unlock it.
It’s a headache to understand it at first, so don’t worry if you cannot get it working the first time and let me apologize for poor explanation.
Well, okay. We added our stuff to the MapActivate() function. When the map starts, the achievement handler will run the think function that we wrote on the scheduler. All that is left now is to define the function.
At the very end of the file (and just above the stock function) we create a new void function that will check if the achievement has been completed, and give it to players if it did. The function must check the relay entity and see if the flag has been set. If so, iterate through all players and set the unlock keyvalue. Delete the relay entity afterwards.
Whoa whoa whoa… made you a little dizzy there, no? Don’t worry, I’ll put a screenshot for you and (try to) explain bit by bit the code you should use.
I suck at coding.
Ahem. The first part is a check to ensure we are still on the same map. While all schedulers are removed on mapchange, there is a very small window of time where it could be possible for the think function to “run” before it is removed. This is because the schedulers are removed on MapActivate() and NOT on MapInit() (There’s no MapInit() in the script!). The chance of this happening is low, but I’d rather be cautious. So anyway, if the map does not match, the function is not run. Hence the return statement.
The next part is locating the info_target entity, and this is done by targetname. If the entity is not found (entity search returns NULL), the function returns.
Afterwards, we get the entity’s custom keyvalues. We use this to extract the value of $i_test, and check if this value equals 1.
Inside of the “if” statement, we iterate through all players. An additional check is done to make sure players are valid and connected.
Finally, we get the player’s custom keyvalues and set the unlock keyvalue ($i_a_unlock) on them. After the “for” loop is done, the relay entity is deleted.
You are probably asking now: “What the heck is the unlock keyvalue?”. You see, since the achievement handler is a separate plugin, it cannot communicate with SCXPM. So we pass a keyvalue to the player, which SCXPM will use to check if an achievement needs to be unlocked.
If that’s the case then “Why 41? Shouldn’t be 40?”. Achievement IDs start at zero: Your new achievement has an ID of 40. However, when SCXPM scans the players keyvalue, it will ignore it if the value equals to zero. Custom keyvalues are initialized with an empty value. In this case, zero.
If SCXPM where to asume that “this” zero is an achievement that needs to be unlocked, the very first achievement will always be triggered, as that one has an ID of 0. That’s why it’s 41. When passing the unlock keyvalue, all IDs must be offset by 1. You want to unlock achievement #40? You pass 41. You want to unlock achievement #0? You pass 1. You want to unlock achievement 27? You pass 28. You get the idea.
Blergh. Explanations aside, if you managed to get it working then: Congratulations! Players will now automatically unlock the achievement when they open that shiny starting door.
If the achievement is an easy one then adding a reward might be pointless. If you don’t want to add a reward to your newly created achievement, then you do not need to do this step.
But… if the achievement is a hard one then you will surely want to add a nice reward to all worthy players that manage to complete it.
Lucky you, adding rewards to an achievement is very easy! Let’s go back to our SCXPM.as file and locate the GiveAchievementReward() function. You will see a switch handling all achievement rewards. Just like before, make a new case at the end of the switch and in there: Be creative! You can be a simple man and give a simple amount of XP, give medals, or show an invite link to your super duper secret discord server for the “uber-l33t” players.
The “engage mode” conditional is there to allow players to make use of the time based event that doubles achievement rewards.
As you can see, this example is really simple. Inside the new case statement, we just display a message to the player, then give the XP reward. (And don’t forget the break statement!).
Done! Your new awesome achievement now has a reward, players will be in for a surprise.
The three steps described above showcased how to make a simple achievement, where to go from there is up to you. You can add additional objectives to an achievement, such as clearing it without deaths. If you can script it, then you can make it an objective. Edit the achievement handler and update/create your new think function to reflect any new conditions you will want to make. Just don’t forget to update GetAchievementMission() as well, players need to know what to do to get that achievement!
It’s going to be a rough road up ahead, specially since I suck at explaining, at coding, and my english sucks. But I believe you can do it.