Category Archives: Defusing

Posts regarding the Game Mode Project “Defusing”

Capturing

One thing about ShootMania that I think is really rewarding is the sound of a captured goal.
So lets take a look at how we can get this capture satisfaction. :)

Important to know is the fact that poles don’t get captured on their own as soon as a player steps on them.
You have to check for this circumstance and set the capture speed of the pole manually.


The goal posts of a map are accessible via the API array BlockPoles and have the class CSmBlockPole.
For managing the capturing progress they have their own CSmGauges.

In the Defusing game mode the attacking team has to plant the bomb and the defending team has to defuse it afterwards. For now I will only focus on capturing and defusing, without actually winning the round.

The bomb will be represented by an Ident variable that holds the id of the (imaginary) bomb carrier. As long as it has not yet been planted the variable has the value NullId. When a pole has been captured the variable will hold the Id of the pole so that I know where the bomb is located. After this point other goals will be ignored and will stay inactive.

Of course only members of the attacking team can plant the bomb (later only the single player carrying the bomb). In Counter-Strike the planting gets aborted as soon as you move. In my ShootMania version I will abort the capturing when the attackers leave the goal posts.
Defusing is only possible by the defending team (the rule about progress abortion applies here as well).

In order to check if a specific player is standing on the goal you can check the Sector of the pole object. It has an array of player ids "PlayersIds".
I simply loop through the array and check which kind of player is standing on the pole at which moment.

if (Player.CurrentClan == Pole.Gauge.Clan) {
// Attacker
}

So I can set the capture Speed to a positive value when an attacker is trying to capture and to a negative value when a defender is trying to defuse the bomb.
For the abortion of bomb activation/defusing I reset the capture progress as soon as no attacker/defender is standing on the pole.

The capture Speed is calculated based on the Max value of the Gauge and the capture time setting I’ve introduced in this version. Via settings players can modify the gameplay parameters to their liking.

Speed = MathLib::NearestInteger(Pole.Gauge.Max / (S_ActivationTime * 1000.));

Pole.Gauge.Max is the maximal value the gauge has to reach in order to actually capture the goal, it’s set to a high value at the beginning of the map.
S_ActivationTime is the setting defining the duration for capturing, it’s set to 5 seconds by default.
* 1000. is necessary to convert the amount of seconds to milliseconds with which the script is working.

In order to defuse an activated bomb defenders have to decapture poles longer than it takes to activate them in the first place. That’s managed by another setting, for now its default value is 1.5 so that defusing takes 1.5x the time as activating. So the activation time is multiplied with S_DefusingTimeFactor and changed to negative for inverting the capture progress.

With each yield; the current value of a Gauge is modified by its Speed value so that after some time the value reaches its Max and an OnCapture event is risen. By handling this event I know that the pole has been captured and I don’t have to check for (Value == Max).


Additionally I’ve set a constant called “CompatibleMapTypes” which restricts the amount of compatible maps. So in my case I’ve set it to “DefusingArena”. You can use several different map types by listing them separated with commas (example: “DefusingArena,RoyalArena,EliteArena”)

There are a few optional constants that you can set for the script.
– Version (shown for example on the server-join-screen),
– CompatibleMapTypes (defining the compatible maps)
– Description (shown in the server-join-screen and via the ingame menu “Help”).


In case you think: “Hey, Bomb Defusal in Counter-Strike doesn’t work like that!”
Yes, I know. The details of the game mode will come with time. It makes sense to focus on a properly working variant before paying attention to details of the specific gameplay.

Without requirements or design, programming is the art of adding bugs to an empty text file.

– Louis Srygley

You can find the current version of the game mode script here: Defusing4.Script.txt

Greetings,
Steff

Preparing the Playground

Having built the map type and a first script test map I will let the mode load and prepare the map properly and let players spawn in their correct spawns.

When the match is about to start I call my own function PrepareMap(). The function initialises the “Bases” of the map and collects all spawns and goals in global arrays.
I save the blocks because then I don’t have to search for them when I need them: For example when I want to check if a player is planting the bomb.
Pay attention to the fact that instead of saving the blocks I only save their ids. It’s an important approach that I will explain in another post.

The ids of the spawn blocks are saved in a two-dimensional array according to their orders which have been set by the map editor while building the map.
So I know which spawns belong to which team.

As bomb spots are represented by goal posts I save their ids as well in an additional array. This one doesn’t need to have any special keys because during each round only one team attacks and tries to plant the bomb while the other team needs to defend. There is no “owning” of goals involved.

Another global variable is the number of the currently attacking team.
This number is generated randomly for the first round of each match and switches between 1 (blue) and 2 (red) before each following round.
So Defusing will for now let the attacking and defending team switch while in the Bomb Defuse mode of Counter Strike you choose your team at the beginning of a match and you will always defend or attack based on that choice.
Later I will probably add a script setting that allows you to use the Counter Strike game play.

In order to properly switch sides I switch the spawn arrays. That may be difficult to understand so I will take a closer look at the idea behind it.

Imagine the spawns array like that:
G_ClanSpawnIds =
[1 => [SpawnId11, SpawnId12], // Spawns Team 1
2 => [SpawnId21, SpawnId22]]; // Spawns Team 2

This is how the array looks after the PrepareMap() function. In the array saved for the key 1 spawn ids for the attacking side are saved while key 2 holds the defender spawns.

Start: The blue team (clan 1) attacks. When I need to spawn a player I can simply take the array referenced by the current clan of the player.

declare SpawnIds = G_ClanSpawnIds[_Player.RequestedClan];

Now I can easily choose one out these spawn ids in order to find a correct spawn for the player.

// Choose a random id out of the array
declare SpawnId = G_ClanSpawnIds[MathLib::Rand(0, G_ClanSpawnIds.count-1)];

// Take the spawn block with the chosen id
declare Spawn <=> BlockSpawns[SpawnId];

The round ends.
For taking all necessary steps before the next round starts if have a function PrepareRound().
An example is that teams need to switch sides, so in order to accomplish that I will simply switch the spawn arrays saved for the keys 1 & 2.

declare Temp = G_ClanSpawnIds[1];
G_ClanSpawnIds[1] = G_ClanSpawnIds[2];
G_ClanSpawnIds[2] = Temp;

Thanks to these 3 lines I don’t have to worry about finding a proper spawn for a player during the running match because I can simply refer to the array of spawns referenced by her/his current/requested clan number.

Additionally to collecting and managing the spawns and poles I constantly update the Clan of the Bases.
So what exactly are “Bases”?
Well, when a map is loaded the game itself automatically connects blocks and pools them into Bases. The blocks considered for these collections are all block types that have lights or colourable elements on them. Especially Spawn, Goals, Heal/Laser/Arrow Pads, Speed Paths and similar stuff.

The class CSmBase has a property Clan that can be used to colour the blocks connected by that base. This colouring gives the map a better look as you spawn in or around blocks that are coloured like your own team. You feel like actually defending your own base instead of just fighting for no reason. That’s why I always try to pay attention to proper colouring of bases.

Each CSmBase needs to be “activated” first though. That’s easily done by setting it active during the preparation of maps. It will turn on the lights of the blocks so that you will be able to see their colours.

Base.IsActive = True;
Base.Clan = 1; // Base will have color of Team 1 (usually Blue)

Now we got a proper map on which the action will take place. Next step will be to actually let the action begin. :)
You can find the current version of the script here: Defusing3.Script.txt

It’s probably quite difficult to understand the steps I’m taking only by reading the posts. A suggestion by me would be to actually load up the script ingame and analyse what the lines I added do. Maybe even try to play around with other values like switching the Base coloring by changing their “Clan”.

I’m sorry if anything is badly described. I still need to figure out which parts I have to explain extensively and which I can leave behind.

Cheerio,
steeffeen

Map Crafting

Today I’ll show you the basic idea behind MapType scripts.
These are necessary in order to build maps that you can use for your game mode. You can’t just use any map as there are usually some requirements a map needs to implement. In the case of Defusing 2 different spawns for the teams and at least one bomb goal will be needed.

So I created the map type script accordingly which you can find here: DefusingArena.Script.txt
I called it DefusingArena because that’s the pattern for map types.

For the map type script I don’t extend another script because it isn’t very complex.

Some important facts that need to be mentioned:

#RequireContext CSmMapType

This directive defines the class in which the script is running. For the game mode the ModeBase script does this assignment for CSmMode. It’s a necessary statement in order to run the script as a map type or a game mode.

CustomEditAnchorData = True;

Enables the editing of anchors. In the map type you have access to anchors that hold metadata about spawns and goals. So you can enable the map makers to assign objectives like the corresponding team of spawns and names of goals.

yield;

yield; causes the script to sleep for a moment. It’s basically a server tick. During yield the environment variables are updated like for example the PendingEvents.
Keep in mind that events are only available for one single server tick, that’s why I process them after each tick in order not to “overlook” one.
In the game mode script yield is called inside the ModeBase script so we don’t have to worry about it.

In my own function UpdateValidability() I check for the presense of all necessary spawns and goals. It’s called whenever the map is modified. By updating the ValidationStatus of a map the flag at the bottom right of the map editor is changed accordingly.
It’s a key feature of map types as invalid maps can’t be used for the game mode and therefore the map type script requires the map maker to build a valid and usable map.

ValidabilityRequirementsMessage = "Error message";
ValidationStatus = CSmMapType::ValidationStatus::NotValidable;

These statements turn the flag red and show an error message when the user clicks on the red flag. That’s useful in order to show the map maker what’s wrong with his map so that he can fix it.
If all requirements are met I set the ValidationStatus to Validated which will turn the flag green.

When the map maker switches into anchor editing mode and clicks on an anchor there’s an EditAnchor event risen.
I handle the event by showing a manialink that offers possibilities to the metadata of spawns or goals.

The manialink is running an additional script which will need to communicate with the actual map type script.

For this process key-value-coding is used. The map type declares variables for the object ManialinkPage that is also available for the manialink script.
As soon as the user clicks on a button like 1 or 2 (representing the team assigned to a spawn) a variable is changed by the manialink script and the map type script recognises it because it’s observing the variable. This key-value-variable could be understood as a “shared” variable between the map type script itself and the manialink script.

The new value will then be assigned to the anchor and when the map is saved and loaded by a game mode the spawn and goal blocks will have the set .Order and .Tag

I will take a closer look on manialink scripts later.

For the development I always build a “ScriptTestMap” for the specific mode. This map serves as a playground for the coming work on the game mode. It usually only has the needed amount of spawns and goal so that you can reach them really quickly and don’t have to run from one side of a big map to the other in order to capture a goal for example.

Closing up I would like to point you to the Links Page I’ve created on which you can find further information about ManiaScript. Especially the ManiaScript Class Documentation is very helpful. It can be generated with the ManiaPlanet game client and covers all available classes and their properties.

For example you can find the validation variables in the class CMapType in case you forget them.
I myself use the documentation all the time in order to look up how exactly the properties are called and for looking up which other useful values the classes offer.

Cheerio,
steeffeen

It’s a Shooter!

While being able to move around is a great thing I’m still missing to be able to hit other players in order to get points and feel great!

The first thing I need are actual opponents.
In the pasts some friends of mine always helped me testing by playing my target or the subject of expriments. Even while creating the 1-line script of the previous posts they joined on my ingame server looking for discovering something new. There have been game modes that I created live while 3 of my friends where playing on my server waiting for new stuff. ;)
Shout-out to them. Thanks!

Of course they aren’t always available so I will need other targets. For that purpose bots come in handy.
There are some things you need to know about bots in ShootMania but I will cover that in another post.
For now I will simply add the following line to my script setting the desired number of bots:

Users_SetNbFakeUsers(2, 2);

With that each team has 2 bots that can be shot.
So far they don’t give points and they can’t even get eliminated though!

When a player performs actions like shooting or hitting other players events are risen. These events need to be handled and processed as by default they are ignored.
In the PlayLoop I will loop through these events and perform actions depending on the type of the events.
At this time only OnHit and OnArmorEmpty events are of interest.

An OnHit event means that a player has hit another player, I will give points based on the damage that was done by the hit. Pay attention that armor and damage is based on a ratio of 100. That means that the usual 2 armor/health you have in most game modes are actually 200 armor in the script.
That means I will divide the damage by 100 in order to get the amount of points I will grant for the hit.

Additionally I will “Discard” (ignore/destroy) events which cover self or team hits.
That’s possible by simply comparing the Shooter and the Victim of the events.

OnArmorEmpty events inform you that a player lost all of his armor, most of the times by being hit by another player but also by falling into OffZone.
If I would Discard these events the player couldn’t die.

Any valid event will be accepted by calling PassOn() with it as parameter. This will make the server process the event so that different actions will be executed. The Shooter will see +1 for each Damage dealt, the victim losing all of his armor will be eliminated and the event feed on the left hand side of the screen will get updated covering the activities that took place.

For managing the points of players I use the script “Score.Script.txt” by Nadeo that gives helpful functions, like summing up points of the current and previous rounds.
The Score object of a player has properties called Points and RoundPoints which are shown separately on the scoreboard. The function AddPoints() of the Score script adds the given amount of points to the RoundPoints.
You need to “inform” this script about the various moments in the game play like starts and ends of rounds because it can’t use the labels like our game mode script does. Currently the end round won’t be called because our round never ends, we will need to add more code in order implement correct rounds later.

What’s worth mentioning is that it’s needed to enable Rounds in the ModeBase in order to use the according labels (StartRound etc).

MB_UseSectionRound = True;

Please see the ModeBase script itself in order to learn more about the structure of it.

I will explain less and less about the script execution in the coming posts as it takes very much time and as it’s kind of boring for more experienced readers. Hopefully you will still be able to follow the development, don’t hesitate to ask if I missed explaining an important fact.

The current version of the script is available here: Defusing2.Script.txt

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.

– Martin Golding

See you around!
steeffeen

Labels are Life

I start the next step with the almost empty script opened in the game as I create my scripts 99% directly inside the game.
(Hint: While having started a match with an own script hitting F12 on the keyboard opens the script editor, making it possible to edit it ingame. Pressing Ctrl+G shows the log.)

If you want to edit your script outside of the game you will need to restart ManiaPlanet after any change as it caches files constantly and it therefore won’t see changes in the code.

I would like to be able to walk around the map needing me to be spawned.
For this act I will use the PlayLoop label of the ModeBase script.

Each label defined in the ModeBase can be used as a point to insert our own code.
Label locations are defined via +++Name+++ or ---Name--- and the actual code for the label is surrounded by a structure like this:

***Name***
***
// Our code
***

Using --- means that only the most derived implementation of the label will be used while +++ uses every implementation. (Other scripts could extend our own script leading to more derivation levels.)

The PlayLoop is the place where you can insert code that’s executed all the time during the actual gameplay. There are other labels like for example StartServer or StartRound in which it’s possible to prepare the used resources or EndMatch in which you will probably announce the winner of the match.

During the PlayLoop I loop through all players in order spawn or unspawn them if needed by checking their SpawnStatus and possible team change or spectating requests.

For spawning I will use an own extra function in order to keep the code more clean as it might get more complex in the future.
Furthermore I use the file SM.Script.txt by Nadeo for the actual spawning action, the script needs to be included with the #Include directive first.

#Include "Libs/Nadeo/ShootMania/SM.Script.txt" as SM
This makes it possible to access functions declared in this script via the namespace “SM”.

Currently the players get simply spawned in the first spawn block I can find.

In order to be able to move around 3 more statements are needed.

UseClans = True;
Activates a team based game play which will be needed for Defusing.

StartTime = Now;
The StartTime defines the begin of the match. Players can only spawn and perform actions after the match has started, which basically means that Now needs to be bigger than or equal to StartTime.

UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
Let players move around with their characters. There are other sequences like for example the Intro or the Podium sequence during which the players will only be able to watch mediatracker clips.

After adding all this stuff and hitting “Save and Test” the script is saved, the match restarted and my character is actually being spawned. I’m able to move around! Yeeyy!

You can find the current version of the script here: Defusing1.Script.txt
I’ve tried to add meaningful comments. ;)

Greetings,
steeffeen

A new World is awaiting us!

Let’s start the ride to our goal of creating a new game mode: Defusing!

As I mentioned I will begin with a clean new file, so I need that.
I simply create an empty text file in the folder Documents/ManiaPlanet/Scripts/Modes/ShootMania/ and will edit it from now on.

Pay attention to the name of the file as it has to end with ".Script.txt" to be considered a valid script file by the ManiaPlanet game.
I simply name my file "Defusing.Script.txt" as the game mode will be called “Defusing”.

The only thing I need to do in order to get an actually running script is adding 1 line of code:

#Extends "Modes/ShootMania/ModeBase.Script.txt"

This line says that I will use another script as base and my own script is only extending the other script.
The ModeBase.Script.txt is a script file that provided by Nadeo and is therefore included in the game itself.
Its structure is handy in order to create a clear and clean game mode script. You will see later why that is.

Now I can already run the script in the game!
SM Storm -> Multiplayer -> Create -> Choose the correct file via the “Script” field -> Hit “Launch” -> Choose any map of your choice -> Click on “Play”

There we are! Our Defusing-Script is running in the game and we just discover a new world (map).
Of course nothing happens so far as the ModeBase we’re extending is only a wrapper and doesn’t actually do anything actively within the match.
The result looks like that.

And the script like that: Defusing0.Script.txt

My next aim will be to let players be spawned in a team-based matter.

If you give someone a program, you will frustrate them for a day; if you teach them how to program, you will frustrate them for a lifetime.

– David Leinweber

So see you next time!
steeffeen