Robert Straughan - Capture Glory: Death Rules
Starting Off Okay, for this module, I decided to keep to just two areas. This is not a tutorial on how to use the toolset, or how to do custom content. I am going to run you through how I came up with the scripts that make this module happen.
Module Properties The first thing I did was removed the default OnDeath, OnDying, and OnRespawn scripts from the Module properties, found in the Edit menu. I then decided what sort of special rules I would want to implement for these. I chose: When a player is reduced to 0 or less hit points, they are automatically killed (ie. there is no bleeding). If the player chooses to respawn, they lose a point of Constitution, as though they had been resurrected. If the player chooses to respawn, they respawn at a point near the beginning of the area.
OnDying In order to ensure that the player actually dies when they are reduced to 0 or less hit points, I wrote the script "tutorial_ody". In it, you will find the following lines: //Script "tutorial_ody" void main() { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(), GetLastPlayerDying() ); } Now, this is probably one of the simplest pieces of scripting you'll find in this tutorial. Be sure to read about functions in the box to the right before continuing. If you look at the definition of ApplyEffectToObject in the box at the bottom of the script editor, you'll see this: //Definition of the ApplyEffectToObject function void ApplyEffectToObject(int nDurationType, effect eEffect, object oTarget, float fDuration=0.0f) Now, the function is a void function, which means it will simply run by itself, as it returns nothing. Notice it has four parameters. We need to fill in that information so that the function knows what to do. The first value is an integer, called nDurationType. We don't have to worry about the name of the parameter, since that is used within the functions code to refer to whatever value we place there. In this case, we have used DURATION_TYPE_INSTANT. Notice that despite the fact this is an integer parameter, the value uses letters. This is because certain constants used by the engine have been given predefined text values so that we can easily know which value to use. In this case, DURATION_TYPE_INSTANT is actually the equivalent of the value zero. Since some functions use values up to 300 or more, its easier for us to use the text equivalents. That way we know what is going on. The next parameter is an effect, called eEffect. In this case, we have gone and put EffectDeath() into it. Look in the editor for this function, and you'll find the following definition: //Definition of the EffectDeath function effect EffectDeath(int nSpectacularDeath=FALSE, int nDisplayFeedback=TRUE) As you can see, this function returns an effect value. Since the parameter requires an effect piece of information, we can place this function into the parameter (told you it was like LEGO). Notice that I haven't entered any information into the parameters of the function, even though it asks for them. That's because the parameters already have values by default. When this occurs, if I don't want to change those values, I don't have to put them in. The final parameter I've entered is GetLastPlayerDying(), for the parameter oTarget. As you can see, oTarget must be an object. Find the GetLastPlayerDying function, and you'll get this: //Definition of the GetLastPlayerDying function object GetLastPlayerDying() This function has no parameters, and returns an object value, so what I've entered will be valid. Note that there are many functions like this, and you must choose the one which matches the event handler that fired the script. In this instance, it was the OnDying handler, so I used the GetLastPlayerDying function. Some other examples include:
Notice that there is one last parameter for the function we are using. A float value called fDuration. This has already been declared by the function as being zero. It is actually used for when we have a DURATION_TYPE_TEMPORARY effect. Since we aren't using one of those, and the value has already been given a default of zero, we can skip it out. And that's it. The script will run whenever a player is between 0 and -10 hit points, and will apply the effect of death to them.
OnDeath Now that we've established that players die when they drop to or below 0 hit points, we need to present them with whatever options they have. To do this, we place a script in the OnDeath handler, with the following lines: //Script "tutorial_odth" void main() { object oPlayer = GetLastPlayerDied(); PopUpDeathGUIPanel(oPlayer, TRUE, FALSE, 1, "If you press respawn, you will be resurrected at the beginning of " + "the level, and will suffer a -1 to your constitution."); } Now, as ever, the void main() means that the script will run whatever is in those curly brackets as soon as the script is fired. In this case, whenever a player actually dies. Notice how I have declared an object. The first line means I have created a value that the script can use. I've made it an object value, and I've called it oPlayer. I could have left it at that. Instead, I've also initialised the value, by giving it a value equal to whatever the GetLastPlayerDied function equals. This is one of those functions that gets an object based on the handler firing this script. The PopUpDeathGUIPanel is the function which makes that set of buttons you get when you die appear. It has a number of paremeters we can give it, as defined below: //Definition of the PopUpDeathGUIPanel function void PopUpDeathGUIPanel(object oPC, int bRespawnButtonEnabled=TRUE, int bWaitForHelpButtonEnabled=TRUE, int nHelpStringReference=0, string sHelpString="") So, another void function, which means it returns no information. This is also an 'internal' function, which is why I don't have to declare it, and can just run it straight away. Bioware has already written and defined this function for me, its not something you need to do yourself, you just use it. The first parameter is an object called oPC, and indicates the object that we wish to target. Notice I place oPlayer into this parameter. Once I've declared oPlayer, and initialised it with a value, I can refer to the declared name from then on to use that object. Notice that even though the next parameter has been declared for me by the function, I've still put it in. This is because I wish to change the values of parameters after that one in the sequence. I cannot skip out a parameter, even if already declared, if I wish to change ones after it. I have changed the next parameter to FALSE. The next parameter I have set to 1, to let the function know that it should show the string I have entered into the last parameter. If in doubt as to what you should put in, read the function definition in the bottom box of the editor. The final parameter is a string value, which is a message put up on the screen when the death panel shows. Before moving on, just to make sure I have made the point clear, consider the following script: //Example Script void main() { object oPlayer = GetLastPlayerDied(); string sMessage = "If you press respawn, you will be resurrected at " + "the beginning of the level, and will suffer a -1 to your constitution."; PopUpDeathGUIPanel(oPlayer, TRUE, FALSE, 1, sMessage); } What does this do? If you've read it correctly, you'll notice it does the exact same thing as the previous script. All that this does differently is that it declares a string called sMessage, and initialises it. I then use the declared name to represent that value in the function's parameter.
OnRespawn Finally, the OnRespawn script. This is where we put everything we've learned from the previous two scripts together. Observe the following: //Script "tutorial_ores" void main() { object oRespawner = GetLastRespawnButtonPresser(); object oWP = GetNearestObjectByTag("RESPAWN_WAYPOINT", oRespawner); ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectResurrection(), oRespawner); AssignCommand(oRespawner, JumpToLocation(GetLocation (oWP))); DelayCommand(0.1f, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectHeal(GetMaxHitPoints (oRespawner)), oRespawner) ); DelayCommand(0.2f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, SupernaturalEffect( EffectAbilityDecrease(ABILITY_CONSTITUTION, 1) ), oRespawner ) ); DelayCommand(0.2f, FloatingTextStringOnCreature( "*constitution decrease*", oRespawner, FALSE ) ); } So, what does it do? By now void main() should be familiar to you. I've declared and initialised two values. Both are objects, and one is called oRespawner while the other is oWP. oRespawner is one of the simple 'get an object based on the handler' functions. But what about oWP. Here, I've used the function GetNearestObjectByTag function. Here's the definition: //Definition of the GetNearestObjectByTag function object GetNearestObjectByTag(string sTag, object oTarget=OBJECT_SELF, int nNth=1) This function returns an object, so I won't get an error for declaring it as an object. The first parameter is a string value. Here, we enter the tag of the object we are after, namely a waypoint with the tag RESPAWN_WAYPOINT. Notice that although the tag for the waypoint does not have the quote marks, the string does. Any string, regardless of whether it contains numbers or letters, must be encased in quote marks. The next parameter is an object. In this function, oTarget has already been declared, but the script is in the OnRespawn handler, which means OBJECT_SELF, is the module. That won't work. I've placed the declared name for the last person to push the respawn button into this parameter, since it is an object. I haven't specified the last value, since I don't need it for the moment. What this function will do is get the nearest object to the person who pressed the respawn button with the tag that I have specified. It will not be able to get objects outside the area that the respawning player is in. Next I use the same ApplyEffectToObject function as I did in the OnDying script, only this time, I've specified that it is using an EffectResurrection() instead. It has no parameters, so I've entered none. The next line is interesting. I've used the function AssignCommand. Look at the definition: //Definition of the AssignCommand function void AssignCommand(object oActionSubject, action aActionToAssign) As you can see, for the object parameter I've given the respawning player as the oActionSubject. But what about this action? The majority of commands that can be given to an object in the module have Action at the front of their names. Some don't have this, but all Action commands are placed into the action queue of the target object. You can also place void commands into the action slot. Why? Well, look at the
listed Action So what does this mean? Well, AssignCommand means we can tell an object other than the calling object to do something. As this is the OnRespawn script, using the action command I've given without this extra function means it would try to get the module to do this action. //Definition of JumpToLocation function void JumpToLocation(location lDestination) This is the action in question. Notice that there is actually an ActionJumpToLocation function. I'm using this one since I don't want the target to queue this command, I want it to happen instantly. The parameter is a location value, and I've used the GetLocation function to specify the location of oWP, as follows: //Definition of GetLocation function location GetLocation(object oObject) The function returns a location value, and has an object value for a parameter. So I can combine the two together, and specify an object within GetLocation, in this case, oWP. Obviously I didn't want the module to jump to the location of oWP, which is why I used AssignCommand. The next two lines are also ApplyEffectToObject functions, with two different effect functions used. They are slightly modified however, using the following function: //Definition of DelayCommand function void DelayCommand(float fSeconds, action aActionToDelay) This is exactly the same in application as the AssignCommand function. It is used to delay the firing of a void function, typically one with an Action in its name. You can delay the function using a float value that represents the number of seconds before the function will fire. Note that the DelayCommand functions will fire themselves as soon as this script has fired. They wait for the specified amount of time, and then trigger the commands they have been given. This does not mean that commands beneath the DelayCommand will wait until the delayed function has fired. The script remains open and comes back for the delayed commands when their time is up. Imagine I'm sitting at the end of a conveyor belt, which is sending random LEGO bricks in my direction. I'm in the process of building a wall when a window shows up. But I'm not ready to place the window. So I put it to one side, and continue building the wall from the bricks the conveyor is sending me. Eventually, I see where I want the window to go, so I pick up the brick and place it. This is how DelayCommand works, the script keeps running, but you can put a function to one side for a while. The first effect used is EffectHeal: //Definition of EffectHeal function effect EffectHeal(int nDamageToHeal) This will heal an amount of damage equal to the integer value we specify in the parameter. In this case, I've used GetMaxHitPoints: //Definition of GetMaxHitPoints function int GetMaxHitPoints(object oObject=OBJECT_SELF) This returns an integer value, but since I don't want to get the hit points of OBJECT_SELF (which is the caller of this script, ie. the module) I have specified that oRespawner should be the value for oObject. The next one is also an odd one: //Definition of EffectAbilityDecrease function effect EffectAbilityDecrease(int nAbility, int nModifyBy) This in itself does not look all that complicated. An effect returning function, with two integer parameters. For the first, we specify ABILITY_CONSTITUTION to tell it we wish to decrease the target's constitution score. For the second, we tell it 1. Note that this is not -1. The function itself has already taken into account that it is subtracting, so had you placed -1 into this parameter, it would add instead. The interesting part of all this is that this effect has been encased within another function: //Definition for SupernaturalEffect function effect SupernaturalEffect(effect eEffect) The duration type for the ApplyEffectToObject function has been set to DURATION_TYPE_PERMANENT, but there's a small nuance with this. It isn't permanent. Not truly. This is to make an effect very long lasting, but dispellable. By placing the effect we wish to apply within this effect returner, we aren't changing anything other than the fact the effect is no longer dispellable. This makes it truly permanent, and not even a script can remove this effect any more. And finally, the last line: //Definition of FloatingTextStringOnCreature function void FloatingTextStringOnCreature(string sStringToDisplay, object oCreatureToFloatAbove, int bBroadcastToFaction=TRUE) Floating text is that slightly transparent text which floats up the player's model on certain events, such as a locked door. To let the player know what is happening, I've decided to add some in for the respawn. It's a void function, so I can delay it. The string value I've typed directly into the function parameter. For the object target, oRespawner makes sure I'm sending the message to the right player. The last parameter I've set to FALSE, since I don't want this message to appear to other party members. A lot of working out what parameters to specify is an application of logical thought, or simply looking at the definition in the box at the bottom of the editor. So, what does this script do? When a player pushes the respawn button, it will resurrect them, send them to the nearest object with the tag I specified, heal them to their maximum hit points, decrease their constitution by 1 permanently, and inform them of the loss.
What have you just learned? Believe it or not, on this one page, you have learned all there is to know about the basics of scripting. If you think you can handle this, move onto the next section. From now on, I'll be going a lot faster. Don't believe me? Well, perhaps you should read through it all again one more time to be sure you understand it. Or better yet, close this tutorial, and try and write the above three scripts in the editor yourself, then see if you got close.
Tasks Here's a task for you. In the OnRespawn script, I healed the player of their hit points. Alter the script so that it heals them by exactly the amount they need to be fully healed.
Hint
|
|
||
Screenshot |
|||
author: Robert Straughan, editor: Grimlar