1. Basics 2. Triggering a scene 3. Scoping 3.1. Creating a new actor 3.2. Loading an actor 3.3. Introducing the actor 3.4. Removing the actor 4. Main script 4.1. Descriptions and Dialogue 4.2. Functions 4.3. Variables and Stats 4.4. Choices 4.5. Conditions 4.6. Loops 4.7. Random 4.8. Commenting, Indentation 5. Testing 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: ActorVar = GeneratePersonTemporary() While !ActorVar.isInterestedIn(Player) ActorVar = GeneratePersonTemporary() EndWhile 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: ID = MySpecialNPC.GetGlobal() if ID != 0 Actor = getSpecific(ID) endif 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 // 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_: is a successful . If you've stored a string in a var, you can also use that: myGift = "chocolates" herPref = "flowers" "I gave her but she said she preferred ." 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: " with "? 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) " 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, ?" 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, ?" 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 0 SO(Angry)::"You never pay any attention to me!" Elseif 1 SO(Happy)::"You always know how to cheer me up." Else SO(Disgusted)::"Well I met today and would you believe she had the nerve to tell me that..." Endif If-structures are organized as follows: If conditionA // opens the if-structure // lines dependent on conditionA being true Elseif conditionB // optional elseif // lines dependent on conditionA being false but conditionB being true ... // there can be multiple elseifs, each one assuming that the conditions above are false Else // optional else: there can be only one // lines dependent on all of the conditions above being false Endif // closes the structure They can also be nested: If conditionA If conditionAA Elseif conditionAB Endif Elseif conditionB if conditionBA Else Endif Endif 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 If conditionA breakup = true // assigns 'true' to the variable Else breakup = false Endif ... If breakup // var's value returns true or false, so this is a valid expression Endif - comparison operators that check the value of a variable or stat against another value or var/stat If MyLocalVar > Actor.interpersonal // greater than Elseif attractiveness < 20 // smaller than Elseif MyLocalVar != 0 // not equal to Elseif Actor.intelligence == 60 // equal to Elseif perversion >= 30 // greater than or equal to Elseif Actor.intelligence <= 20 // smaller than or equal to Endif - a combination of the above, held together with logical operators: && means AND: If Actor.isMale() && Actor.isDating() " is my boyfriend." Endif || means OR: If Actor.isColleague() || Actor.isBoss() "It was odd seeing outside of work." Endif 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: While intoxication < 80 Creep(Grin)::"Can I ply you with another drink?" Player(Happy)::"Sure, I can do with another..." intoxication += 10 endWhile Player(Drained)::"I think I've had far too many..." 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: Random New_Person(Flirty):: "Hey there good-looking, fancy meeting you here!" New_Person(Flirty):: "! Is that you, a sight to admire as always? How is it going?" New_Person(Flirty):: "I knew it was you right away, hottie! How is life treating you?" endRandom Random Guest1 = getPerson() Guest1 = generatePerson() endRandom 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: If conditionA // some code doing something If conditionAA // some more code here Else // why not here too Endif Endif 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)