Writing Scenes

1. Basic structure
A 'scene' in the game is a virtual novel-type showing of characters with dialogue and choices, potentially leading to a sex scene. Scenes are built by a script in an .lpscene file, which is typically structured as follows:

// trigger conditions

WHAT:

WHERE:

WHEN:

WHO:

OTHER:

// some code can happen before the scene UI triggers

SceneStart        // brings up scene UI

// scene script goes here

SceneEnd            // closes scene UI, removes newly generated chars & sticks them back in their lpcharacters

timeout(200, sceneID) // cooldown on the scene appearing again

2. Triggering a Scene
At the top of the scene are a few fields where you can stipulate basic conditions for the scene to trigger randomly.

WHAT: sleep, masturbate

// lists the activities that can trigger the scene using their ID, separated by commas. See the 'Actions' folder of loaded modules to figure out which are available. Alternatively, you can use 'all' and exempt certain actions by prepending them with a - symbol:

// WHAT: all, -sleep

// If you don't want to put anything here, add 'none'

// WHAT: none

WHERE: home, office

// lists locations that trigger the scenes. See the Docs/Lists/All_wheres.txt for locations. You can also use all and -home, for instance, as with WHAT.

// If you don't want to put anything here, add 'none'

// WHERE: none

WHEN: 9-21

// time periods when scene is available, can roll over a date transition (22-4)

WHO: Actor = getSpecific(Dating)

// Assigns an actor id to the "actor" variable. This variable can be named whatever, and is simply a way of knowing if an actor with the specific scope is available and immediately being able to refer to them in the rest of the script. See 3.: scoping. Multiple actors can be introduced, separated by a semi-colon:

// WHO: Dating = getSpecific(Dating); Affair = getSpecific(Affair); Actor = getSpecific(Dating_Friend)

// Additional conditions depending on the actor(s) can be added after this with a semi-colon and a leading 'If', like

// WHO: Actor = getSpecific(ExDating); If !Actor.isContactExchanged && Random(20,100) < Actor:rapportwithplayer

// If the game fails to find an appropriate actor or if the following If-condition returns false, the scene will be skipped.

// If you don't want to put anything here, just add 'none'

// WHO = none

OTHER: !isWithCompanion

// other conditions

Alternatively, you can bypass these trigger conditions if you enter the name of the scene to the SCENE_ALWAYS field of an action file. For an example in the base game, see

Modules/vin_Base/Actions/Social/organize_a_house_party.lpaction

Modules/vin_Base/Scenes/Social/house_party.lpscene

house_party can still trigger randomly, but if you organize it yourself, it immediately triggers. In order to know if it was force-triggered or not, to make sure your text and code can follow up on that, you can use the ForcedTrigger condition.

3. Scoping
Scoping is necessary for any scene involving more than the player. It winds down to either creating a new actor or finding an existing one, and then storing their ID in a variable for use in the scene.

Scoping can happen inside or outside the main script, as well as in the WHO condition on top.

3.1. Creating a new actor
There are two ways of creating a new actor to introduce to your player's life: a permanent one and a temporary one.

GeneratePerson creates an actor who will stay in your game permanently, and assigns their actorID to the variable you call the function on:

ActorVar = GeneratePerson

Between those parentheses, you can specify any actor presets you want to use (see the Presets folder of the modules). For instance, if you want to create a bodybuilder male, you type:

ActorVar = GeneratePerson(bodybuilder)

You can combine presets too:

ActorVar = GeneratePerson(easterneuropean, twenties, fitness_model)

While this already gives you some control over who is created, it might not be enough. Certain data such as gender preference and most actor stats is entirely randomized on creation, so you may need to create a few actors before you land on the right person. Or you may simply want to create an actor who's only needed for the duration of the scene, but shouldn't stay in your game permanently.

GeneratePersonTemporary creates such a non-permanent actor, and uses the same presets as GeneratePerson. Sex with temporary actors doesn't count as an affair.

For example, if you want to only generate an actor who's interested in your gender, you could use a while loop to generate temporary actors until you find the right one:

After that, you can still make the temporary actor permanent, by using the makePermanent function:

ActorVar.makePermanent

And it's possible that you want to apply a preset to a person after they're already generated:

ActorVar.blendPreset(bodybuilder)

When you do so, however, their face and hair will most likely be set to the default for that preset, causing some repetition. You can mix things up a little with:

ActorVar.randomizeFace

ActorVar.randomizeHairs

3.2. Loading an actor
In order to find a (permanent) actor to use in your scene, you have the following options:

- "Player" is you. Yes, you! "Player" is a special global var that always contains the player's actorID.

- "CurrentCompanion" is a special global var that'll always hold your current companion's ID so you can use it right away. Its function counterpart is getCompanion, which is largely pointless.

CurrentCompanion.dress

is the same as

ActorVar = getCompanion

ActorVar.dress

- getPerson gets a random actor.

ActorVar = getPerson         // get a random, permanent actor

ActorVar = getPerson(true)     // get a random actor whose number you have

ActorVar = getPerson(false) // get a random actor whose number you don't have

- getSpecific gets a specific actor. You can stipulate which actor by

- keyword: Dating (bf/gf), Dating_Friend (friend of your bf/gf), Boss, Colleague, Neighbour, PT (personal trainer), ExDating (ex-bf/gf), Landlord, CurrentBabyDaddy (father of player's unborn child), Impregnated (carrying player's child)

- ID: every actor has a reference number that you can get with the getID function. You can store it in a global var and look it up later in order to find them back again:

ID = ActorVar.GetID            // 'ID' is a local var

MySpecialNPC.SetGlobal(ID)    // there now is a 'MySpecialNPC' global var with the value previously stored in 'ID'

and then in another script:

3.3. Introducing the actor
Once you have an actor selected for your scene, chances are you want him or her to actually show up:

Actor.show

Perhaps they should be dressed first though, so let's type

Actor.dress

Actor.show

You can specify where on the screen the actor is to show up with a number, stating their position, from right to left:

Actor.show(2)

3.4. Removing the actor
Once you no longer need or want an actor in the scene, you can remove them:

Actor.hide

When the scene ends, all actors are removed from the scene UI automatically.

4. Main script
The main script will typically consist of a sequence of descriptions and dialogue, functions, operators, and choices. Everything is written and read from top to bottom.

Its structure is usually given shape with 'if'-conditions, and sometimes 'while'-loops or 'Random' structures.

4.1. Descriptions and Dialogue
Scene descriptions provide narration and are added between double quotation marks:

"I heard a knock on the door. I wonder who it is."

Dialogue is also between double quotation marks, but is preceded by the actor speaking it:

Player:: "Oh, it's you, . What a shame though,  just went out and won't be back for another few hours."

Actor(Happy):: "Yes, I know.  told me."

As you can tell, you can direct the emotion of the actor by inserting a "mood" keyword in parentheses after the actor. A list of available mood keywords is in Docs/Lists/All_moods.txt.

Both scene descriptions and dialogue can dynamically insert text based on an actor var. This is called interpolating, and is marked by < >.

             // Actor's first name

         // Actor's last name

    // Player's family relationship to the actor

        // Actor's family relationship to the Player

 or   // Subject pronoun: I, he, she - with or without capitalization

 or  // Possessive adjective: my, his, her

 or  // Object pronoun: me, him, her

 or  // Reflexive pronoun: myself, himself, herself

 or <Actor.pp> // Possessive pronoun: mine, his, hers

In order to choose between other words to inject, based on the actor's gender, you park them between _or_:

<Actor.name> is a successful <Actor.man_or_woman>.

If you've stored a string in a var, you can also use that:

myGift = "chocolates"

herPref = "flowers"

"I gave her <myGift> but she said she preferred <herPref>."

4.2. Functions
All existing functions are listed in /Docs/Modding. Each file under Docs/Modding/Command_Functions and Docs/Modding/Condition_Functions describes the use of a single function.

If you're unsure what you're looking for, consider using the Lists/Functions_By_Theme.txt file, which groups functions according to the context in which they're used.

Let's go over what's typically in one of them.

- The top line is a short example showcasing the function's syntax.

Actor.addColleague

- USE: this field describes what the function is for.

- TYPE: this field tells you what kind of function it is. Eg a command or a condition, a reference function (meant to run on an actor) or not.

- RETURNS: if the function returns a data type, this field will tell you which one.

- THEME: keywords describing the context in which this function is used (see Lists/Functions_By_Theme.txt)

- COMPARE: other functions relevant to this one

- EXAMPLE: an example in context

4.3. Variables and Stats
Three types of variables are available:

- local ones you use in the script

- global ones you use with the getGlobal and setGlobal functions

- actor stats

Local variables can be added at any point by simply assigning them a value with "=". You don't need to declare them first. The values can be numbers, actorIDs, boolean (true/false), as well as strings:

Choices = 7                        // stores number

Friend = getPerson(true)          // stores actorID

Dating = Friend.isDating         // stores true or false

DateType = "Hang Out"                // stores string, this can be injected in a description or dialog: "<DateType> with <Friend.name>"?

Global variables are stored outside of the script and can be consulted in any other script. You create them by giving them a name and setting them to a value (either explicit or contained in a local var):

myGlobalVarName.setGlobal(5)

myGlobalVarName.setGlobal(myLocalVar)

They are consulted by passing them to a local var:

myLocalVar = myGlobalVarName.getGlobal

You delete them like this:

myGlobalVarName.clearGlobal

Actor stats are added to the game with separate .lpstat files in the module (/"Stats"). They are consulted on an actor by connecting the actor var with the actor stat's ID with a colon:

if 20 > actorA:perversion

attr = actorB:attractiveness

If you don't specify the actor, the game will assume you mean to check the stat on the player:

if 30 < perversion

is the same as

if 30 < Player:perversion

Unlike local vars, you cannot assign a value to actor stats with '=' but must use '=>' instead:

actorA:attractiontoplayer => 20

Local numeric and actor variables can be easily manipulated with 'assignment operators', setting their value to the result of a calculation involving themselves:

somefloat += 1            // the same as somefloat = somefloat + 1

somefloat -= 2

actor:somestat *= 0.8

actor:somestat /= 2

4.4. Choices
If you want to give the player a choice between several options on how to proceed further in the scene, you set them up with consecutive integers starting with 0, followed by two colons:

SO = getSpecific(Dating)

"<SO.name> has hardly spoken a word all night. What do I do?"

0:: "Enjoy the peace and quiet."

1:: "Tell a joke"

2:: "What's wrong, <SO.name>?"

You can also make certain choices only selectable if they meet a certain condition:

0:: masochism < -20:: "Enjoy the peace and quiet."

1:: interpersonal > 30:: "Tell a joke."

2:: "What's wrong, <SO.name>?"

Make sure that one option is always selectable so that the player doesn't get stuck. In order to follow up on these choices, you're going to need if-conditions.

4.5. Conditions
Conditions make sure that certain code is only executed under certain circumstances. Conditions are already part of the top triggers of a scene, the triggers of an action file, or choices, but you can also add some in the main script with an if-structure.

If-structures are useful for any kind of branching that you want to see in the scene, and essential to follow up on previous choices:

If-structures are organized as follows:

They can also be nested:

Apart from conditions following up on choices, any condition is always boolean: it only evaluates whether the entire expression that follows is true or false.

To make certain that your expression will be valid for a condition, it should contain either

- (boolean) condition functions, which return 'true' or 'false'

If Actor.livesWithPlayer

If isWithCompanion etc.

In order to check the reverse of a boolean function, you prepend it with the not symbol (!):

!Actor.liveswithPlayer

- vars containing 'true' or 'false' as a value

- comparison operators that check the value of a variable or stat against another value or var/stat

- a combination of the above, held together with logical operators:

&& means AND:


 * means OR:

OR takes precedence over AND, so the following

If Actor.isColleague && Actor.isMale || Actor.isBoss

is only true if the actor is

either male or your boss

AND a colleague

which, given how colleagues and bosses are different groups, means that it will never be true for your boss. You probably meant for the condition to always and only be true for either bosses or male colleagues.

If so, you can use brackets [] to override the hierarchy:

If [Actor.isColleague && Actor.isMale] || Actor.isBoss

It's never a bad idea to add brackets, if only just for readability.

Just like with boolean functions, any expression inside brackets can be preceded by the NOT operator: !

If ![Actor.isColleague && Actor.isMale]

4.6. Loops
A while-loop repeats code and lines as long as the conditional expression that follows 'while' is true:

For another example on using while-loops, refer back to 3.1.

The most important thing to know about a while loop is that you must absolutely make sure that it isn't infinite. If the expression has no chance of ever becoming false, the loop will just keep excuting what's inside it forever, which usually results in crashing the game.

While Player.isPlayer

// crash: the player is always going to be the player

endWhile

While 30 > attractiveness

// crash if attractiveness isn't increased inside the loop

endWhile

4.7. Random
You can randomize code and text lines by placing them between a Random and endRandom block, giving each code or text line an equal chance of being displayed:

4.8. Commenting, Indentation
You can comment out a line by prepending it with a double slash

// This is a comment: it won't be executed by the game, but helps you keep track of what parts of the script are supposed to do

If conditionA // what's behind this double slash is also a comment

Indentation makes the overall structure easier to follow, and has been used throughout this document already:

While most people are used to using the Tab key for indentation, there seems to be a problem with getting LifePlay files to recognize them. If you're using a proper text editor, you can find an option to convert Tabs to clusters of spaces. For Notepad++, that's in Edit/Blank Operations/TAB to Space. Just run it when you're done.

5. Testing
Once you're done with a scene, you'll probably want to give it a try and see if it works. Rather than waiting for it to trigger randomly, you can

- open the console with #

- type in the scene's ID, eg meet_new_person

A scene's ID is its file name, without the extension.

If the # key doesn't work for you, you can specifiy a different one in the game's Config/DefaultInput.ini file. Find and edit the following line:

+ActionMappings=(ActionName="TestScenes",Key=#,bShift=False,bCtrl=False,bAlt=False,bCmd=False)