Difference between revisions of "Missions and NPC Interaction"
Zonkmachine (talk | contribs) m (→Persistent characters: Missed from previous edit.) |
Zonkmachine (talk | contribs) (Repair bold formatting from previous tags.) |
||
Line 6: | Line 6: | ||
Missions have always used characters, to a certain degree. Bulletin board missions always display a face and a name, and that name will often be recorded on the current mission list. Traditionally, though, there has been no mechanism by which a character can be stored, and retrieved by a later mission, perhaps one that is not related at all. | Missions have always used characters, to a certain degree. Bulletin board missions always display a face and a name, and that name will often be recorded on the current mission list. Traditionally, though, there has been no mechanism by which a character can be stored, and retrieved by a later mission, perhaps one that is not related at all. | ||
− | NPCs defined using the | + | NPCs defined using the '''Character''' class retain their name, gender, physical appearance and so forth. They can be used during the course of a mission script, and when they are no longer required they can be saved into a pool of reusable characters. |
Stored in the character's object are several values. By default, the character has five personality attributes, and four skills attributes. More can be added to any character at any time by a script. | Stored in the character's object are several values. By default, the character has five personality attributes, and four skills attributes. More can be added to any character at any time by a script. | ||
Line 14: | Line 14: | ||
=== Creating characters === | === Creating characters === | ||
− | Characters are created using the class constructor, | + | Characters are created using the class constructor, '''Character.New()'''. This returns a character object, complete with a name, gender, face, inherited default values and inherited methods. Defaults can be specified by passing a table of values as a parameter. |
=== Selecting characters that already exist === | === Selecting characters that already exist === | ||
− | Of course, the whole point of persistent characters is that they are reusable. All reusable characters are stored in a global array, named | + | Of course, the whole point of persistent characters is that they are reusable. All reusable characters are stored in a global array, named '''PersistentCharacters'''. The '''Character''' class provides two methods for searching for characters for use, as well as plenty of information to help a mission script decide between the various characters that might be on offer. The most useful of these is '''available''', which is a simple boolean flag indicating that the character is not currently being used by another mission. |
− | The simplest method for finding characters is | + | The simplest method for finding characters is '''Character.FindAvailable()'''. This method returns an iterator function, suitable for use in a Lua '''for''' loop. It returns each character in PersistentCharacters where '''available''' has been set to '''true'''. In this example, we look through all the available charcters, and find the first one whose navigation skill is above 50; otherwise, we create a new character with the desired attribute. |
<pre>local SelectedPerson | <pre>local SelectedPerson | ||
Line 30: | Line 30: | ||
end | end | ||
SelectedPerson = SelectedPerson or Character.New({navigation=51})</pre> | SelectedPerson = SelectedPerson or Character.New({navigation=51})</pre> | ||
− | The other method is much more versatile. | + | The other method is much more versatile. '''Character.Find()''' returns an iterator function, in the same way the '''Character.FindAvailable()''' does. By default it returns all persistent characters. What makes it so much more powerful, though, is that it can take a Lua function as a parameter. That function must take a '''Character''' object as an argument, and must return a boolean value. It will be used by '''Character.Find()''' as the basis for selection of characters. Using this method, the above example could be written like this: |
<pre>local Selector = function (character) | <pre>local Selector = function (character) | ||
Line 42: | Line 42: | ||
end | end | ||
SelectedPerson = SelectedPerson or Character.New({navigation=51})</pre> | SelectedPerson = SelectedPerson or Character.New({navigation=51})</pre> | ||
− | Of course, Selector could be arbitrarily complex, and can be re-used as often as necessary. Each character has details of the last time that a script finished using it, as well as the location where that event took place. These are stored in the | + | Of course, Selector could be arbitrarily complex, and can be re-used as often as necessary. Each character has details of the last time that a script finished using it, as well as the location where that event took place. These are stored in the '''lastSavedTime''' and '''lastSavedSystemPath''' attributes. These can come in very useful for selecting characters in a realistic fashion. |
=== Using the character === | === Using the character === | ||
− | The first thing to do with a character is to mark it as unavailable, so that other Lua scripts won't choose to use it. The | + | The first thing to do with a character is to mark it as unavailable, so that other Lua scripts won't choose to use it. The '''CheckOut()''' method will set the character's '''available''' flag to '''false'''. In addition, it performs some checking, and returns a boolean value. If it returns '''true''', it is indicating that the checkout operation was successful, and that another script hadn't checked the character out in the mean time. The first example above could be refined to take this into account: |
<pre>local SelectedPerson | <pre>local SelectedPerson | ||
Line 59: | Line 59: | ||
=== Releasing a character for other scripts to use === | === Releasing a character for other scripts to use === | ||
− | Once a character has been finished with by a script, it can be returned (or added) to the pool of available characters using the | + | Once a character has been finished with by a script, it can be returned (or added) to the pool of available characters using the '''Save()''' method. |
<pre>SelectedPerson:Save() | <pre>SelectedPerson:Save() | ||
SelectedPerson = nil -- Free up this variable; the character is safely stored</pre> | SelectedPerson = nil -- Free up this variable; the character is safely stored</pre> | ||
− | + | '''Save()''' checks the '''PersistentCharacters''' array for the presence of this character, and if it isn't there, it inserts the character into that array. It then sets '''available''' to '''true''', and updates the '''lastSavedTime''' and '''lastSavedSystemPath''' attributes. | |
=== De-persisting a character === | === De-persisting a character === | ||
− | If a character is to be retired for any reason (perhaps unused characters need to be pruned, or one just suffered death at the hands of the plot) then it can be removed from the | + | If a character is to be retired for any reason (perhaps unused characters need to be pruned, or one just suffered death at the hands of the plot) then it can be removed from the '''PersistentCharacters''' array using the '''UnSave()''' method. |
<pre>DeadPerson:UnSave()</pre> | <pre>DeadPerson:UnSave()</pre> | ||
− | This removes the character, but does not destroy it. It would be perfectly possible to re-insert the character with | + | This removes the character, but does not destroy it. It would be perfectly possible to re-insert the character with '''Save()'''. Of course, once all other references to the character have gone, the character is lost completely and will be destroyed by the Lua garbage collector. |
== Using a Character object == | == Using a Character object == | ||
Line 76: | Line 76: | ||
Each character has a number of attributes and skills. By default there are five personality attributes and four crew skills attributes. More can be added at any time by a script, simply by defining them on the fly. | Each character has a number of attributes and skills. By default there are five personality attributes and four crew skills attributes. More can be added at any time by a script, simply by defining them on the fly. | ||
− | The default personality attributes are | + | The default personality attributes are '''luck''', '''intelligence''', '''charisma''', '''notoriety''' and '''lawfulness'''. |
=== Personality attributes === | === Personality attributes === | ||
Line 82: | Line 82: | ||
==== Luck ==== | ==== Luck ==== | ||
− | The | + | The '''luck''' attribute should probably be the least used attribute of a character. It is used to test how fortunate a character is, in the absence of more appropriate attributes. |
==== Intelligence ==== | ==== Intelligence ==== | ||
− | The | + | The '''intelligence''' attribute is intended to reflect the character’s ability to learn and solve problems. When a crew candidate is asked to perform a test, intelligence shows up as 'general' in the results. |
==== Charisma ==== | ==== Charisma ==== | ||
− | The | + | The '''charisma''' attribute can be used to determine whether a character manages to win a contract, or reach a favourable agreement with another party. It reflects the character's confidence, body language and general likeableness. |
==== Notoriety ==== | ==== Notoriety ==== | ||
− | The | + | The '''notoriety''' attribute reflects the manner in which a character's reputation precedes them. This could mean fame, or infamy, or just being known for having a good time. It could be combined with '''lawfulness''', for example; a notorious and unlawful character is very different from the notorious and lawful character who is probably trying to apprehend them. Both are likely to be generally recognised, whereas the non-notorious and unlawful character who just picked somebody's pocket is not. |
==== Lawfulness ==== | ==== Lawfulness ==== | ||
− | The | + | The '''lawfulness''' attribute shows where a character stands regarding the law. It's not necessarily evidence of criminal behaviour, but whether a character is likely to uphold the law, or ignore it. A lawful character would be less likely to accept work as crew on a pirate ship. An unlawful character would be less likely to take up an honest career when there are more profitable options. |
==== Player Relationship ==== | ==== Player Relationship ==== | ||
− | The | + | The '''playerRelationship''' attribute describes how much the character likes, or gets on with, the player. A low score means they despise the player, while a high score means they adore the player. |
=== Crew skills attributes === | === Crew skills attributes === | ||
Line 108: | Line 108: | ||
==== Engineering ==== | ==== Engineering ==== | ||
− | The | + | The '''engineering''' attribute is intended to reflect the character’s mechanical, electrical or other tecnical skills. An engineer can install equipment on a ship, repair minor damage and keep the ship running smoothly. |
==== Piloting ==== | ==== Piloting ==== | ||
− | The | + | The '''piloting''' attribute is intended to reflect the character’s skill at flying spacecraft. Above a certain value, the game could use this stat to allow a ship crewed by this character to fly on autopilot, even when one is not fitted. |
==== Navigation ==== | ==== Navigation ==== | ||
− | The | + | The '''navigation''' attribute is intended to reflect the character’s skill at course plotting, mapping and so on. A good navigator could perhaps gain additional range on a hyperspace jump, or succeed in identifying a location based on clues, etc. |
==== Sensors ==== | ==== Sensors ==== | ||
− | The | + | The '''sensors''' attribute is intended to reflect the character’s ability to get the most from a ship’s scanner, radar, etc. This character might be able to find hidden ships, identify unknown cargo in space and so forth. |
== Using character attributes == | == Using character attributes == | ||
− | These attributes are numeric, and are designed to be used in a similar way to the atrtibutes on a table-top role-playing game character sheet. The | + | These attributes are numeric, and are designed to be used in a similar way to the atrtibutes on a table-top role-playing game character sheet. The '''Character''' class has methods for testing these attributes against simulated dice rolls. |
− | The | + | The '''DiceRoll()''' method rolls four virtual sixteen-sided dice, and returns the sum of their results. [https://gist.github.com/1315972 Here's a probability table.] |
− | + | '''TestRoll()''' performs a dice roll using the method mentioned above, and returns a boolean value; true means that the test passed, that the dice result was numerically less than the attribute being tested. The higher the attribute, the more likely it is to pass the test. The natural range of the dice is 4 to 64, with a most probable result of 34. The attribute's range can be much greater than that. '''TestRoll()''' can take an optional second argument, which is a modifier; the modifier is added to the attribute for this roll only, and the chance of any result changes accordingly. | |
If the result of the dice roll was very low or very high (below 9 or above 59) then it was a critical success, or a critical failure, respectively. The result is the same, but there is a side-effect that the character's attribute is permanently affected. A critical loss reduces the attribute by one, a critical success increases it by one. The chances of either happening are about 1%. | If the result of the dice roll was very low or very high (below 9 or above 59) then it was a critical success, or a critical failure, respectively. The result is the same, but there is a side-effect that the character's attribute is permanently affected. A critical loss reduces the attribute by one, a critical success increases it by one. The chances of either happening are about 1%. | ||
Line 142: | Line 142: | ||
table.insert(crew,PotentialCrewMember) | table.insert(crew,PotentialCrewMember) | ||
end</pre> | end</pre> | ||
− | If, for some reason, you really do not wish to modify the attribute, then there is also a | + | If, for some reason, you really do not wish to modify the attribute, then there is also a '''SafeRoll()''' method, which acts almost exactly the same, except that it has no critical success or failure system. |
=== Arbitrary attributes === | === Arbitrary attributes === | ||
− | Any arbitrary attribute can be set in character, and tested using | + | Any arbitrary attribute can be set in character, and tested using '''TestRoll()''' or '''SafeRoll()'''. It's as simple as defining a new member value. It can be done in the constructor, or afterwards: |
<pre>c = Character.New({juggling=45}) | <pre>c = Character.New({juggling=45}) | ||
Line 158: | Line 158: | ||
== ChatForm faces == | == ChatForm faces == | ||
− | It's trivial to use a character on a | + | It's trivial to use a character on a '''ChatForm''' in a BBS ad. The '''Character''' class can be passed directly to the '''ChatForm.SetFace''' method, placing the character's name, face and job title (if it is set) on the form. |
<pre>ch = Character.New() | <pre>ch = Character.New() | ||
form:SetFace(ch)</pre> | form:SetFace(ch)</pre> |
Revision as of 12:16, 1 November 2021
Non-player characters can be defined and stored easily using the Character class. Character provides an object which represents a simple character sheet, along with a set of methods with which to operate on it.
Contents
Persistent characters
Missions have always used characters, to a certain degree. Bulletin board missions always display a face and a name, and that name will often be recorded on the current mission list. Traditionally, though, there has been no mechanism by which a character can be stored, and retrieved by a later mission, perhaps one that is not related at all.
NPCs defined using the Character class retain their name, gender, physical appearance and so forth. They can be used during the course of a mission script, and when they are no longer required they can be saved into a pool of reusable characters.
Stored in the character's object are several values. By default, the character has five personality attributes, and four skills attributes. More can be added to any character at any time by a script.
Characters do not have to be persistent; they can be created and discarded for single missions very easily. If stored as persistent characters, then they immediately become available for other missions to use.
Creating characters
Characters are created using the class constructor, Character.New(). This returns a character object, complete with a name, gender, face, inherited default values and inherited methods. Defaults can be specified by passing a table of values as a parameter.
Selecting characters that already exist
Of course, the whole point of persistent characters is that they are reusable. All reusable characters are stored in a global array, named PersistentCharacters. The Character class provides two methods for searching for characters for use, as well as plenty of information to help a mission script decide between the various characters that might be on offer. The most useful of these is available, which is a simple boolean flag indicating that the character is not currently being used by another mission.
The simplest method for finding characters is Character.FindAvailable(). This method returns an iterator function, suitable for use in a Lua for loop. It returns each character in PersistentCharacters where available has been set to true. In this example, we look through all the available charcters, and find the first one whose navigation skill is above 50; otherwise, we create a new character with the desired attribute.
local SelectedPerson for PotentialPerson in Character.FindAvailable() do if PotentialPerson.navigation > 50 then SelectedPerson = PotentialPerson break end end SelectedPerson = SelectedPerson or Character.New({navigation=51})
The other method is much more versatile. Character.Find() returns an iterator function, in the same way the Character.FindAvailable() does. By default it returns all persistent characters. What makes it so much more powerful, though, is that it can take a Lua function as a parameter. That function must take a Character object as an argument, and must return a boolean value. It will be used by Character.Find() as the basis for selection of characters. Using this method, the above example could be written like this:
local Selector = function (character) return character.available and (character.navigation > 50) end local SelectedPerson for PotentialPerson in Character.Find(Selector) do SelectedPerson = PotentialPerson break end SelectedPerson = SelectedPerson or Character.New({navigation=51})
Of course, Selector could be arbitrarily complex, and can be re-used as often as necessary. Each character has details of the last time that a script finished using it, as well as the location where that event took place. These are stored in the lastSavedTime and lastSavedSystemPath attributes. These can come in very useful for selecting characters in a realistic fashion.
Using the character
The first thing to do with a character is to mark it as unavailable, so that other Lua scripts won't choose to use it. The CheckOut() method will set the character's available flag to false. In addition, it performs some checking, and returns a boolean value. If it returns true, it is indicating that the checkout operation was successful, and that another script hadn't checked the character out in the mean time. The first example above could be refined to take this into account:
local SelectedPerson for PotentialPerson in Character.FindAvailable() do if PotentialPerson.navigation > 50 and PotentialPerson:CheckOut() then -- This character is ours now! SelectedPerson = PotentialPerson break end end SelectedPerson = SelectedPerson or Character.New({navigation=51})
Releasing a character for other scripts to use
Once a character has been finished with by a script, it can be returned (or added) to the pool of available characters using the Save() method.
SelectedPerson:Save() SelectedPerson = nil -- Free up this variable; the character is safely stored
Save() checks the PersistentCharacters array for the presence of this character, and if it isn't there, it inserts the character into that array. It then sets available to true, and updates the lastSavedTime and lastSavedSystemPath attributes.
De-persisting a character
If a character is to be retired for any reason (perhaps unused characters need to be pruned, or one just suffered death at the hands of the plot) then it can be removed from the PersistentCharacters array using the UnSave() method.
DeadPerson:UnSave()
This removes the character, but does not destroy it. It would be perfectly possible to re-insert the character with Save(). Of course, once all other references to the character have gone, the character is lost completely and will be destroyed by the Lua garbage collector.
Using a Character object
Each character has a number of attributes and skills. By default there are five personality attributes and four crew skills attributes. More can be added at any time by a script, simply by defining them on the fly.
The default personality attributes are luck, intelligence, charisma, notoriety and lawfulness.
Personality attributes
Luck
The luck attribute should probably be the least used attribute of a character. It is used to test how fortunate a character is, in the absence of more appropriate attributes.
Intelligence
The intelligence attribute is intended to reflect the character’s ability to learn and solve problems. When a crew candidate is asked to perform a test, intelligence shows up as 'general' in the results.
Charisma
The charisma attribute can be used to determine whether a character manages to win a contract, or reach a favourable agreement with another party. It reflects the character's confidence, body language and general likeableness.
Notoriety
The notoriety attribute reflects the manner in which a character's reputation precedes them. This could mean fame, or infamy, or just being known for having a good time. It could be combined with lawfulness, for example; a notorious and unlawful character is very different from the notorious and lawful character who is probably trying to apprehend them. Both are likely to be generally recognised, whereas the non-notorious and unlawful character who just picked somebody's pocket is not.
Lawfulness
The lawfulness attribute shows where a character stands regarding the law. It's not necessarily evidence of criminal behaviour, but whether a character is likely to uphold the law, or ignore it. A lawful character would be less likely to accept work as crew on a pirate ship. An unlawful character would be less likely to take up an honest career when there are more profitable options.
Player Relationship
The playerRelationship attribute describes how much the character likes, or gets on with, the player. A low score means they despise the player, while a high score means they adore the player.
Crew skills attributes
Engineering
The engineering attribute is intended to reflect the character’s mechanical, electrical or other tecnical skills. An engineer can install equipment on a ship, repair minor damage and keep the ship running smoothly.
Piloting
The piloting attribute is intended to reflect the character’s skill at flying spacecraft. Above a certain value, the game could use this stat to allow a ship crewed by this character to fly on autopilot, even when one is not fitted.
The navigation attribute is intended to reflect the character’s skill at course plotting, mapping and so on. A good navigator could perhaps gain additional range on a hyperspace jump, or succeed in identifying a location based on clues, etc.
Sensors
The sensors attribute is intended to reflect the character’s ability to get the most from a ship’s scanner, radar, etc. This character might be able to find hidden ships, identify unknown cargo in space and so forth.
Using character attributes
These attributes are numeric, and are designed to be used in a similar way to the atrtibutes on a table-top role-playing game character sheet. The Character class has methods for testing these attributes against simulated dice rolls.
The DiceRoll() method rolls four virtual sixteen-sided dice, and returns the sum of their results. Here's a probability table.
TestRoll() performs a dice roll using the method mentioned above, and returns a boolean value; true means that the test passed, that the dice result was numerically less than the attribute being tested. The higher the attribute, the more likely it is to pass the test. The natural range of the dice is 4 to 64, with a most probable result of 34. The attribute's range can be much greater than that. TestRoll() can take an optional second argument, which is a modifier; the modifier is added to the attribute for this roll only, and the chance of any result changes accordingly.
If the result of the dice roll was very low or very high (below 9 or above 59) then it was a critical success, or a critical failure, respectively. The result is the same, but there is a side-effect that the character's attribute is permanently affected. A critical loss reduces the attribute by one, a critical success increases it by one. The chances of either happening are about 1%.
So, imagine the player wants to hire a crew member. The player has a long criminal record, so the potential crew member might not want to join the crew. To find out, we perform a test roll:
if PotentialCrewmember:TestRoll('lawfulness') then UI.Message('Sorry, I just don't feel comfortable joining your crew') -- Update the time and place, and release this character PotentialCrewmember:Save() else UI.Message('I would love to join your crew!') table.insert(crew,PotentialCrewMember) end
If, for some reason, you really do not wish to modify the attribute, then there is also a SafeRoll() method, which acts almost exactly the same, except that it has no critical success or failure system.
Arbitrary attributes
Any arbitrary attribute can be set in character, and tested using TestRoll() or SafeRoll(). It's as simple as defining a new member value. It can be done in the constructor, or afterwards:
c = Character.New({juggling=45}) c.sewing = c.DiceRoll() -- Randomize this attribute! if not c:TestRoll('juggling') then UI.Message(c.name .. " dropped a ball") end
Perhaps there isn't much requirement for juggling or sewing on board a ship in Pioneer, but it does serve to illustrate that there are no limitations.
ChatForm faces
It's trivial to use a character on a ChatForm in a BBS ad. The Character class can be passed directly to the ChatForm.SetFace method, placing the character's name, face and job title (if it is set) on the form.
ch = Character.New() form:SetFace(ch)