Author Topic: [Tutorial][7.2] Story mod makeing with explained examples  (Read 4934 times)

Offline The Bigfoot

[Tutorial][7.2] Story mod makeing with explained examples
« on: May 11, 2015, 10:47:49 pm »
Tutorial: Annotated Story mod tutorial

As there is very little information on how to write a mod and incorporate it into the game, I thought I would write this to help anyone who wanted to try it out. This was written at the same time I coded the mod so it should be a good example of how to structure writing a mod as well as the actual coding.

It is written so that no Python needs to be known, but introduces some basic code that will be invaluable for story mods.

The mod we will make is about Kayto and Ava watching a news broadcast about the fall of Cera.

The finished mod will include:

1. Seamless inclusion of the mod into the game
2. A completely stupid plot
3. Kayto and Ava talking
4. Changing sprites and making backgrounds
5. A battle with a modified ship
6. A new minor character
7. A very important choice
8. A new planet!
9. A working store item
10. NO alterations to script or existing files in the game

Attached, there are some rpy files that could be useful. They are designed to enhance modifiability and increase mod compatibility. These non-standard functions are written in red. They are designed to work with 7.2 and using them will possibly break the mod when there are updates, but I will do my utmost to re-code them for each new version.

Ok...

Requirements

The Sunrider game is obvious and the only other requirement is a simple text editor (you can use almost any but I highly recommend Notepad++).

Setup

Setup

What is RenPy and how does modding work?

Renpy is the engine for Sunrider and for our purposes is a very flexible, fantastically complex interpreter. When it is run it scans all the rpy files in its directory and acts on them. As rpy files are simple txt files, editing them is very easy.

The majority of what you will write is written in RenPy language which is very similar to Python crossed with English. Even without knowing python, you can code a great deal.

Before we type anything, you need to know basic formatting. RenPy code is made out of blocks, these can be labels or code segments but a bock is always started with a colon and text in that block is indented 4 spaces. Text should not exist outside a block unless commented out.

Code: [Select]

label Text:
    This is a block
    text is indented by 4 spaces

init:
    An init block is run when Sunrider is launched or a save is loaded

python:
    A python block is comprised entirely of python code

init python:
    This python block is run at the same time as an init block but can only contain python code.

In addition, unless your text editor is set up to convert tabs, donít use them as they will choke RenPy up.

To start, you need to navigate to the Sunrider directory and under game, create a txt file. Rename it as Newsmod.rpy (if you donít have file extensions visible then open it and save as Newsmod.rpy and use the all files format).

Open it up and then select whatever program you want to modify it in.

Now we can start coding. It is best if you type in all the code in code blocks rather than copy/pasting from this tutorial as this will help you familiarise yourself with its structure and make it easier to write your own.


Basic mod building

Labels and Launching
Code: [Select]
label News: # The start of the news block

    ava "Captain! The Alliance is broadcasting a report on the PACT advance and the attack on Cera!" # defined speaker

    "Kayto" "They have to start mobilising soon, put it on the main screen." # Named text


Right, there are four things to look at here.

1. label News: This is the start of the News block and all code that follows it and is indented belongs to that block. When the indent is removed then that block is ended.

2. ava "text" This is text spoken by Ava. ava is defined in the core game files, you will see how to define your own later.

3. "Kayto" "text" This is an alternative way to write text, the first doublequote is the speaker, the second is the text. To have no speaker, just use "text".

4. # Is a comment. Any text to the right of it is ignored by the program so it is useful for documentation of your own code or to remove some temporarily. This is the single exception to breaking indentation.

Now that we have a few lines of text, we can run the mod. Always do this when you try something new and try to follow the code at the same time as you test the mod.

The problem is we have no way to call our label. There are three native ways to integrate the mod into the game without modifying core files and these will come later but for now we will run it manually. There is an additional way to call mods added in the Functions zip.

At the start of the mod, add this:
Code: [Select]
init python: # init blocks run at the start of the game or on loading
    config.developer = True # Enables the dev console
    def Newsmod(): # Defines a function
        renpy.jump("News") # Python version of jump

This init block will run when Sunrider is launched and enable the dev console, to access it, start a new game or load a save and press Shift + O. Then jump to the label News by typing Newsmod() into the console.

The Dev console is very useful as it lets you call functions, jump to points in the story and reload without closing and opening Sunrider. (Shift + R)

--------- Results Breakdown ---------

1. The mod showed us some text but no images
2. The mod kept its background image
3. After the mod ran through its lines it returned to Main Menu

There were no images because they need to be displayed by the show command (which we will cover next). By the same token, the background stayed the same. If you set a background and then jump to an original scene, that scene may not have had to change the background before so you need to do it yourself at the end of your label.

After the mod ran then it returned to Main Menu because after you jump to a label, the game reads down through the text, if it runs out then it returns to menu. This is very bad and you should ALWAYS direct code at the end of a label, even if the label you want is directly below in your file.

To move to a label, use jump lablename, or if your label was called, use return to bounce back to the label it was called from.

Now lets work on images...
Backgrounds and Sprites!
Adding Images

Code: [Select]

label News: # The start of the News Block.

    show bg bridge # show background bridge
   
    show ava uniform altneutral angry with fade: # with fade is how it enters
        xpos 0.5 # X axis location for middle of screen

    ava "Captain! The Alliance is broadcasting a report on the PACT advance and attack on Cera!" # defined speaker
    kay "They have to start mobilising soon, put it on the main screen." # kay is the shorthand for kayto speaking
    "Ava turns to the screen" # With no speaker quotes, this is just placed on the screen.
   
    show ava uniform altneutral angry with move: # moves rather than places, angry because she normally is!
        xpos 0.3
   
    ava "It looks like their showing our escape..."
    return


The major difference between show and scene is that scene wipes all images on that layer, making it useful but not mandatory for backgrounds

show on the other hand just displays the image, making it perfect for sprites

Transformations are added to the image name to control how it appears, and the xpos is the x position on the screen (0 to 1). There are more ways to display an image but it is best to look them up in the RenPy documentation. Remember, showing an image uses a block for position code! That means you need a : and to indent the xpos code.

Code: [Select]
Show:

Syntax:

        show imagename with transformation # Basic show image.

        show imagename with transformation: # Lets you choose where on the x axis it is.
                xpos x

        scene imagename # Displays a scene, wipeing all previous images.

for example;
show ava uniform altneutral angry with fade:
    xpos 0.5
      -or-
scene bridge

Hide:

Hide imagename

#just like show, you can use transforms but you don't need to be so explicit as with show. hide ava will work just as well as hide ava uniform altneutral angry

##Transformations list##

with fade: - Fades in or out
with dissolve: - Dissolves in or out
with pixellate: - pixilates for .5 seconds
with move: moves existing sprite

More at http://www.renpy.org/doc/html/transitions.html#transitions

To find character sprites, look in Sunrider/Game/Character and in most cases just copy the filename and remove the underscores, most of them are already defined.

If you want to change ava from angry to happy, just show her with the new sprite and it will change her, thereís no need to hide her first.

---------Breakdown---------

show is a very easy keyword, just remember that if you are moving labels you may need to change a background before you do!

---------Breakdown---------

Now lets get into the meat of a story mod... Choices

Story-changeing choices!
There is nothing more thrilling than life or death choices, they are the meat of a good mod, its just a shame you arn't writing one!

Ok so follow the drill and edit your code to...:
Code: [Select]
    ava "It looks like their showing our escape..."   

    menu: # Menu block, everything is indented.
        ava "Do you want any popcorn?" # Is not a new block so displays normally
       
        "Yes": # [u]Is[/u] a new block so this is a choice
            kay "Watching us almost get killed does make me hungry..." # Indented
            jump Popcorn # Jump to label Popcorn
       
        "No":
            kay "No thanks, just a diet coke."
            jump Nopopcorn
           
label Popcorn:
    "Ava passes you the popcorn and you settle into your seats as the lights dim"
    jump setupmissionnews
   
label Nopopcorn:
    "Ava shrugs and passes you the bottle. You had better watch those expensive command consoles!"
    "As you settle back into your chairs, the lights start to dim in preparation"
    jump setupmissionnews

Well... That was just thrilling. Controlling trivial things like that makes this feel almost like a stat management game! Fortunately after we go through the code we can move on to something genuinely exciting.

You can use menu: either as the start of a menu block or as an actual label. The difference is that you can jump directly to the choice if you make it a label. This helps if you want to show or hide options by variable.

Code: [Select]
# For this more complex example, we will improve code already in the game
# From the end of beach_episode where you choose who to talk to.
# The game currently has a choice for all the girls and reduces a counter before re-writeing the menu for each of them.
# This means wasted code and people can be picked twice.
# This menu will use variables to track who has been spoken to and remove them from the choices.

#Original code:
    $ beachtalk = 3
   
    menu:
        "Asaga":
            jump beachasaga
        "Chigara":
            jump beachchigara
        "Ava":
            jump beachava
        "Icari and Kryska":
            jump beachicarikryska
        "Sola":
            jump beachsola
        "Claude":
            jump beachclaude
   
label beachasaga:
   
    $ beachtalk -= 1
    $ affection_asaga += 1

#asaga words
#retyped menu

Instead we will do
    $ ava, asa, chi, ica, sol, cla = 0,0,0,0,0 # defines all variables at 0
    $ bt = 3
    menu beachmenu:
            kay "(Now, what should I do?)"

            "Talk to Asaga" if asa == 0 and bt > 0: # if asa equals 0 and bt is greater than 0 return true.

                $asa = 1 # disables this choice.
                $beachtalk -= 1 # beachtalk = beachtalk -1

                jump beachasaga

            "Help Ava with the Barbecue" if beachtalk == 0:
                jump afterbeachtalk

label beachasaga
    text
    jump beachmenu

# This creates a loop that sends you to the label for the character you pick, In this case Asaga and then disables the choice.
# After the beach talk, you are bounced back to the menu.
# When you have spoken to three people then you are given only one choice, to help Ava and the story continues.


This creates compact code that is easy to read and options which cannot be reused.

Setting Variables
Ok, we are near the end of the Basic section, the last thing that is important to a story mod is setting variables.

Variables are vital to track story paths, manipulate menus and pretty much everything interactive. This will not use code for the module but it is still important to learn.

Setting a variable:
Code: [Select]
#There are a number of different variable types, python sorts them for you so all you need to do is supply the information.

# To assign a variable, use name = value.
# Values can be text, numbers, lists of values and a number of advanced things like tupples and dictionarys or other values.

The value type is determined by how the value is added, for example

String = "Hello!" # a string is a string of characters, either letters or numbers. they always have quotation marks to distinguish them from variables
 
integer = 1 # an integer is a whole number, it has no special container characters

float = 1.1 # as integer but not limited to a whole number.

list = [value1, value2] # a list of values

variable = T # variable becomes the value of T

You can use almost any name for a value such as chi or bt in the previous section.

Variables are trivial to set and easy to use, as seen in the menu:

Code: [Select]
T = 1
If T == 1: # if true T is equal to 1, then:
    T += 1 # T is equal to T + 1 (T becomes 2)

#This was used in the menu example to make choices appear and disappear. They do however need to be set before they can be mentioned in the code, otherwise there is an error.

#When you use variables, python sees the content so if you say:

X = 5
T = 2
E = T * X

#python sees

X = 5
T = 2
E = 5 * 2


Thatís all there is to it. Its very simple and with use it will become natural. The only thing to remember is that when python code is used outside a python block, you need to add a $ before it. like $ E = T * X

Now take a break and let it all soak in. If you were observant you might have realised after the menu, we are programming a battle!

Battle Making

Starting a battle!
Does it feel good to be back to your crummy little mod? I hope so because its about to get awesome!

Battles at first seem quite complex so I have split it into three parts. First of all, lets look at the setup code:

Code: [Select]
   

    jump setupmissionnews

label setupmissionnews:
    window hide # Hides message bar
    $ BM.mission = 'news' # tells Battle manager mission name
    call missionnewsinit # sets mission variables
    jump battle_start # starts the battle


Well that was short and disappointing!
Effectively it hides the message bar and sets BM.mission to 'news'. This is important because of how battles work and you should always be careful how you name your missions

Then a label called missionnewsinit is called, call means the label. is jumped to and when it is returned it returns to the point it was called.

Then we jump to the battle_start label, this isnít one we set so we just leave it alone.

Note: If you use a string for the battle ID ($BM.mission = 'whatever') then the formation phase is skipped. If you want your mission to have formation placing then use an integer that is above 12  like ($BM.mission = 67).

The next segment is better I promise.
The Shipyard

In the missionxinit section we set up the enemy ships, place our own on the map and make any cover or set music. Making your ships placeable will come later.

Here is the code, remember donít be lazy!
Code: [Select]
    jump battle_start # starts the battle
   
label missionnewsinit:
       
    python:                                # /
        zoomlevel = 1                      #| This code is used in every mission init
        enemy_ships = []                   #| It just cleans the battlefield
        destroyed_ships = []               # \

        sunrider.set_location(1,1)    # Comment out ships that you havenít met yet!
        #blackjack.set_location(2,2)  # These are coordinates to place the ship
        #liberty.set_location(3,3)
        #phoenix.location = None   # Set location to None to use in battle, there is a better way to do this but its simple for now
        #bianca.set_location(5,5)   
        #seraphim.set_location(6,6)
        #paladin.set_location(7,7)

        BM.xadj.value = 872 # set battle view, doesnít really need to be changed ever
        BM.yadj.value = 370

        create_ship(MissileFrigate(),(13,5)) # This creates a ship with default armament
        create_ship(MissileFrigate(),(15,5),[PactFrigateMissile()]) # Creates a modifed ship
        enemy_ships[-1].max_en=80
        enemy_ships[-1].boss = False
        enemy_ships[-1].Faction ='PACT'
        enemy_ships[-1].name = 'Damaged Missile Frigate'
        enemy_ships[-1].max_hp = 100
        enemy_ships[-1].hp = 30
       
        create_cover((8,4)) # create cover
        create_cover((1,1))

    $ PlayerTurnMusic = "music/Titan.ogg" # set music
    $ EnemyTurnMusic = "music/Dusty_Universe.ogg"
    return #goes back to setupmissionnews where it was called

Ok then, lets work through this.

The label moves immediately into a python block, but thatís not really important here, lets break it up.

Code: [Select]
        zoomlevel = 1                      #| This code is used in every mission init
        enemy_ships = []                   #| It just cleans the battlefield
        destroyed_ships = []               # \

The first three commands are nothing of import, you can just use them time and again without needing to change them.

Code: [Select]
        sunrider.set_location(1,1)    # Comment out ships that you havenít met yet!
        #blackjack.set_location(2,2)  # These are coordinates to place the ship
        #liberty.set_location(3,3)
        #phoenix.set_location(4,4)   # Set location to None to not use in battle
        #bianca.set_location(5,5)   
        #seraphim.set_location(6,6)
        #paladin.set_location(7,7)

The second set are placing your own ships. You are running a python function embedded within your ship objects (donít worry if you donít understand, its not important right now) and telling it to use the co-ordinates you give it.

sunrider.set_location(1, 1) will set the sunrider to the top left corner.

The ship location doesnít change when the battle is won so even if the ship is commented out, if you have already met it, it will spawn there. To not use the ship in a battle, set location to None (no quotes, this is a keyword).

For a better way to remove the ship from battle, then remove it from player_ships and BM.ships. This will be dealt with later.

Code: [Select]
        BM.xadj.value = 872 # set battle view, doesnít really need to be changed ever
        BM.yadj.value = 370
Again these two are inconsequential, just where the battlemap view starts.

Code: [Select]
        create_ship(MissileFrigate(),(13,5)) # This creates a ship with default armament
        create_ship(MissileFrigate(),(13,5),[PactFrigateMissile()]) # modify a ship
        enemy_ships[-1].max_en=100
        enemy_ships[-1].boss = False
        enemy_ships[-1].Faction ='PACT'
        enemy_ships[-1].name = 'Damaged Missile Frigate'
        enemy_ships[-1].max_hp = 100
        enemy_ships[-1].hp = 30
The first line in the code tells the game to make and place a MissileFrigate at 13, 5. Its very easy to use and in the second file attached, there is a list of names you can use for it.

create_ship() is the function name, it is called by having brackets on the end.
MissileFrigate() is the ship object, the brackets are important. You can find a list in the library rpy.
[PactFrigateMissile()] # This is a list of weapons that are assigned to the ship. If not used then it uses whatever default is assigned to it. Again the () is important.

The second block is making another ship but modifying its attributes. It uses enemy_ships[-1] to access the last ship in that list (this is the last as it was most recently created and all enemy ships go in this list).

Code: [Select]
        create_cover((8,4)) # create cover at 8, 4
        create_cover((5,4))

These commands make cover as found when you first meet Sola.

Code: [Select]
    $ PlayerTurnMusic = "music/Titan.ogg" # set music
    $ EnemyTurnMusic = "music/Dusty_Universe.ogg"
    return #goes back to setupmissionnews where it was called
The last commands set the music on each turn and the return command bounces us back to setup as this label was started by call lablename. Just note that they intentionally out of line from the python block to show that the python code needs to have $ in front.

Now we have only one more segment to cover and we can start the battle!
Begin the Fight!
Code: [Select]
label missionnews: # This is the actual mission, very simple
   
    $ BM.battle() # Calls your turn and waits for a response
    if BM.battlemode == True: #When set to false battle ends
        jump missionnews #Loop Back
    else:
        jump after_missionnews

Ok first note that missionnews was not called by us. This is why we need to be consistent with the naming on our labels, otherwise BAD THINGS happen.

This is just a basic loop, whenever an action occurs, it runs and if the battle is over, it jumps to after_missionnews, otherwise it loops again.

If you want to script the battle, then enter it between the $BM.battle() and if BM.battlemode == True: lines. This will be dealt with in depth later. For now, we have very little interaction with this code.

The final thing to note is the jump if battlemode isnít True, that is where you go after the battle. The victory screen comes before then.

Now just add this to the end of the code...
Code: [Select]
label after_missionnews:
    "Newsperson" "So at least some of the Cera military are known to have survived the PACT assault"
    kay "So... At least someone in Cera may know we survived"
    ava "But PACT are more than likely to come after us now we're in the public view."
    jump dispatch # shipmap
And you are ready to launch your mod!

Do so now to check it all works, have a break and then head on to Integrating the Mod. If it doesn't work then just check the code against mine and try to find any spelling mistakes. The error screen on RenPy is quite good at pointing out where the problems are.
Complete Battle Code
Code: [Select]
label setupmissionnews:
    window hide
    $ BM.mission = 'news'
    call missionnewsinit
    jump battle_start

label missionnewsinit:
       
    python:                                # /
        zoomlevel = 1                      #| This code is used in every mission init
        enemy_ships = []                   #|
        destroyed_ships = []               # \

        sunrider.set_location(1,1)    #comment out ships that you haven't met yet
        #blackjack.set_location(2,2)  # These are coordinates to place the ship
        #liberty.set_location(3,3)
        #phoenix.set_location(4,4)   #set location to None to not use in battle
        #bianca.set_location(5,5)
        #seraphim.set_location(6,6)
        #paladin.set_location(7,7)

        BM.xadj.value = 872 # set battle view
        BM.yadj.value = 370

        create_ship(MissileFrigate(),(13,5)) # create a ship
       
        create_ship(MissileFrigate(),(15,5),[PactFrigateMissile()]) # modify a ship
        enemy_ships[-1].max_en=80
        enemy_ships[-1].boss = False
        enemy_ships[-1].Faction ='PACT'
        enemy_ships[-1].name = 'Damaged Missile Frigate'
        enemy_ships[-1].max_hp = 100
        enemy_ships[-1].hp = 30
       
        create_cover((8,4)) # create cover
        create_cover((5,4))

    $ PlayerTurnMusic = "music/Titan.ogg" # set music
    $ EnemyTurnMusic = "music/Dusty_Universe.ogg"
    return #goes back to setupmissionnews where it was called
   
label missionnews: # This is the actual mission, very simple
   
    $ BM.battle() #Continues battle
    if BM.battlemode == True: #When set to false battle ends
        jump missionnews #Loop Back
    else:
        jump after_missionnews

Hooking into the game.
How?
We already know to get to a label you use a jump command. However it is a bad idea to alter the core files as these will be wiped whenever the game is updated and could clash with other mods.

Without being able to alter the core scripts, you need to look at init blocks.

These arenít called by the script but automaticly run whenever the game launches so you can use them to sidestep into the game through one of three (four) ways.

1. You can Hijack a label
2. You can make your own Planet.
3. You can make an item in the Store.
4. You can add a talk button to the Sunrider map.

Hijacking a label. is good for minor inserts or edits to the story, it is useful to redirect a jump or make text/code changes. This is entirely seamless but has the potential to cause mod conflicts (although the chances are low and there are methods to make this very unlikely.

Making your own planet is quite simple and although there is a lot of code, especially if you use it to show a transit screen, I have made a rpy file that lets you just plug in names of labels and map images that is suited to most cutscenes. The downside to using only this method is a planet will appear on the galaxy map and the player will have no idea why, or even miss it entirely.

Making an item for the store is a nice blend of the two but it is flawed as a method of including your own content. It is extremely adaptive as when someone buys an item you can trigger any number of functions, variable changes and jump to labels but it is also hard for the player to see what will happen and hard to alert them to it. It is best used as an enabler such as setting a talk on the ship map to active or revealing a planet on the galaxy map. This may require a lot of python (if you make your own events) or as little as setting a variable.

Adding a chat button to the ship map is the most unobtrusive method of adding mod content but it also can be hard to 'aim' and requires basic understanding of python. This should really be used as a do-once option rather than a more persistent planet.

So for pure story edits, use hijacks. For additions to the story, a combination of hijacks, store items, planets and chat buttons works very well.

In the following segments, there are examples of each method, however they will need to be adjusted to fit your mod. Everything you need to change is something you will have seen. Try and get each method to work and then combine them
so you use all three (or four) at once.

If you cant do it then have a look at the finished mod code at the end of the post and see if you can find how it was done, then try and do it without copy-pasteing. Feel free to ask questions as I am sure many people on the forum will be happy to help.

Setting Attributes
Before going further, attributes will need to be covered.

Unfortunately as init blocks are ran as the game starts, they will overwrite standard variables, therefore resetting your mod. Object Attributes however are a more substantial type of variable. They are available throughout the mod (even in functions without being declared global) and are easy to check for.

Attributes can be any of the variable types including functions and are referenced by object.attribute. These are what we were tweaking in the battle init section.

RenPy games natively have a store object so assigning variables to that is a convenient thing to do.

You would add it like this.

Code: [Select]
python init:
    if not hasattr(store,'variablename'): # This triggers if the attribute has not been set
        store.variablename = value # sets the attribute
        #This space can also be used to create objects like planets or store items

Of course what variables and objects you need to store depends on how you intend to implement the mod and variables.
Label Hijacking
You can use a RenPy config code to replace a label with your own like this.

Code: [Select]
init python:
    config.label_overrides['original label'] = 'your label'
Its exactly that simple.

Example:

init python:
    config.label_overrides['checkformissions'] = 'checkformissionsmod'

label checkformissions:
    #text

    kay "Well, more money for us. I can't complain about smashing up pirates for some quick credits. What about the other one?"
   
    ava "The last mission could be more complicated."

    ava "The Union has lost a number of transport ships recently, they were long-range tug ships that were intended to move small asteroids and carry them through warp jumps. These ships, had an advanced homing beacon on-board and have been tracked to deep space, where they appear to be moving under engines only."

    # text

    $ knowaboutmod = True

    jump afterbeachcarry
Planetary Construction
A planet is simply an object with certain attributes, much like a ship. If it is detected when the galaxy map opens, it is placed on there and when clicked it jumps to a label.

This can be anything from a text label. to a mission selection or briefing.

Code: [Select]
init python:
    Planet("MODPLANET", "News", x, y, "condition")

MODPLANET is the name of the planet on the galaxy map
"News" is the label. it loads on clicking
x + y are coordinates for the map
"condition is If true, planet is visible"

Put simply, "condition" checks everything in the quotes, and if it returns True, the planet is displayed.

Code: ( example) [Select]
Knowmod = 1
TalkedAva = True
TalkedChigara = False

    Planet("EXAMPLE", "Label", x, y, "Knowmod == 1") # if Knowmod is 1, planet is visible.
    Planet("EXAMPLE", "Label", x, y, "TalkedAva== True") # if TalkedAva is True, planet is visible.
    Planet("EXAMPLE", "Label", x, y, "True") # Planet is always visible
    Planet("EXAMPLE", "Label", x, y, "TalkedAva == True and TalkedChigara == True") # Because TalkedChigara is False, even though TalkedAva is True, planet is not useable.

#You can use the variable name on its own if it is set to True
    Planet("EXAMPLE", "Label", x, y, "TalkedAva and TalkedChigara") # is the same as the previous example.


At the moment this is quite unimpressive, clicking the planet just sends you to the News label and starts the mod.

I have created a quick method of making the sunrider jump screen but if you dont want to use the rpy files (although this one will not break with updates), you can still set it manually.

If you want to  have missions spawning there then instead of directing it straight to News, do the following code:

(Manual)

Code: ( Mission Selection Screen:) [Select]

init python: # create the planet

    Planet("ModPlanet", "Modplanetwarp", 1370, 450, "True")
#   Planet("PLANET NAME", "LABEL", X, Y, "CONDITION")

label Modplanetwarp: # This is the label in the planet object. RENAME THIS

    $ map_back = "Modplanetback" #plug in the label to go back (below)
   
    if modmission == 0: # set a variable if you want the mission(s) to be available, you can have three missions visible at once but it dosent line up well.
        $ galaxymission1 = True
        $ mission1 = "Newswarpto" #Mission Label (Where you want to go if its clicked)
        $ mission1_name = "Memory: Escape from Cera."

    else: # If the variable isent matched then these missions are used (all set to false atm)
        $ galaxymission1 = False
        $ mission1 = None
        $ mission1_name = None
    jump showmodplanet

There are a number of things here that you need to replace to let this work for your own mod.

1. Use your own label name
2. Use your own map_back (explained in the next code block)
3. Use your own mission variable
4. Set your mission name and mission label

Code: (Show planet code:) [Select]
#This is triggered when you click the planet on the galaxy map

label showmodplanet:       
    scene bg black
    show galaxymap:
        zoom 1 alpha 1
        parallel:
            ease 0.5 alpha 0
        parallel:
            ease 1 xpos -13710 ypos -5190  zoom 10
    show "Map/Farport.jpg": # *** This is the planet image.
        zoom 0.0268041237113402
        xpos 1071 ypos 519 alpha 0
        parallel:
            ease 1.1 alpha 1
        parallel:
            ease 1 zoom 1 xpos 0 ypos -430
    pause 1
    show "Map/farport_info.png": #*** This is the infobox (It can be skipped if you want)
        xpos 1098 ypos 200
    call screen map_travelto
    with dissolve

label planetback: # this is used to return from the planet map to the galaxy map
    hide "Map/farport_info.png" # *** This is the infobox
    scene bg black
    show  "Map/Farport.jpg": # *** This is the planet image.
        zoom 1
        xpos 1370 ypos 450 alpha 1
        parallel:
            ease 0.5 alpha 0
        parallel:
            ease 1 zoom 0.0268041237113402 xpos 1490 ypos 725
    show galaxymap:
        xpos -14900 ypos -7250 zoom 10 alpha 0
        parallel:
            ease 1.1 alpha 1
        parallel:
            ease 1 xpos 0 ypos 0 zoom 1
    pause 1
    call screen galaxymap_buttons

Right then... With this you need your own custom labelnames and you need to replace the planet image and the infobox image. These are marked in the code block with ***

Other than that its very simple and can largely be left alone.

Code: (Warpout code:) [Select]
label Newswarpto:
   
    $ Random = renpy.random.randint(1,9) # sets random integer for below text.

    if Random == 1:       
        scene space back1 
    if Random == 2:       
        scene space back2 
    if Random == 3:       
        scene space back3 
    if Random == 4:       
        scene space back4 
    if Random == 5:       
        scene space back5 
    if Random == 6:       
        scene space back6 
    if Random == 7:       
        scene space back7 
    if Random == 8:       
        scene space back8 
    if Random == 9:       
        scene space back9 
                                                     
    show sunrider_warpout_standard:                   
        xpos 700 ypos 350                             
    with dissolve                                     
                                                     
    pause 1.0                                         
                                                     
    play sound "Sound/large_warpout.ogg"             
    show sunrider_warpout_standard_flash:             
        xpos 426 ypos 0 alpha 0                       
        linear 0.1 alpha 1                           
        linear 0.1 alpha 0                           
    show sunrider_warpout_standard out:               
        xpos 700 ypos 350                             
        ease 0.2 xpos 200 ypos 300 zoom 0             
                                                     
    pause 1.0                                         
                                                     
    show "Map/Farport.jpg": # *** Insert planet image here
        ypos 0
        ease 1.5 ypos -120   
    with dissolve                                   
    pause 1                                           
                                                     
    show sunrider_warpout_standard out:               
        xpos 2300 ypos 1200 zoom 2                   
        ease 0.2 xpos 1000 ypos 500 zoom 0.5         
    pause 0.2
    play sound "Sound/large_warpout.ogg"
    show cg_legionwarpin_missilefrigate_warpflash:   
        zoom 1.5 xpos 1550 ypos 750               
    show sunrider_warpout_standard
    pause 2.0
    jump News

With this, all you need to do is replace the label with the one for your planet, replace the planet image (again marked by ***) and replace the jump at the end with your mod's label.

And its done, the Sunrider now warps to the planet when the mission is clicked and you can start your scene.

This is quite substantial code and it is quite bulky to put in a mod, especially if you have more than one planet to deal with. Fortunatly a lot of the code is just left alone and dosent change between planets.

In the Functions.zip there is a Rpy called Transit that lets you do all of this with under 20 lines of code, not includeing planet definition.


Code: [Select]
#The select mission part is very simmilar to the previous code.

    Planet("ModPlanet", "Modplanetwarp", 1370, 450, "True")
#   Planet("PLANET NAME", "LABEL", X, Y, "CONDITION")

label Modplanetwarp: # Have one of these for each planet
    image modbg2 = "Map/Farport.jpg"
    image modinfo2 = "Map/farport_info.png"

label missionselectionlabel: #Make a custom label for your planet

    $ map_back = "planetback" # No need to touch this
   
    if modmission == 0:
        $ galaxymission1 = True         
        $ mission1 = "Missionselected" #Label to jump to for mission, make one per mission.
        $ mission1_name = "Memory: Escape from Cera." #mission name

    else: # If the variable isent matched then these missions are used (all set to false atm)
        $ galaxymission1 = False
        $ mission1 = None
        $ mission1_name = None
    jump showmodplanet

label Missionselected:
    $ modjumplabel = "News" # This is the label for the mod content.
    jump transit

To use this:
1. Create one label per planet defineing its image and infobox (modbg2 and modinfo2)
2. Create one label per mission setting modjumplabel to its label
3. Set mission variables

The other bits of code are tucked away in transit.rpy

Store method
The store has been set up to automaticly add storeobjects found in store.mod_items. This is a godsend, look at this code!

Code: [Select]
init python:

    class Activeatemod(StoreItem): # needs to be a unique name
        def __init__(self):
            StoreItem.__init__(self)
            self.id = 'Activatemod' # needs to be unique, I normally use the class name
            self.display_name = "Click to play" # name in store
            self.cost = 2000            #cost
            self.tooltip = "Clicking me does something"
            self.visibility_condition = "Test == 1" # (Visible if Test is equal to 1) If something is true or it is equal to True, then this is visible.
            #This can be set to a false value in the buy function so it is not re-buyable.
           
        def buy(self):   #buy function         
            global Test # This needs to be here so that the Test variable updates
            Test = 0 # This would let you make the item invisible
            store.variablename = value # update an object attribute
            Event() # This is triggered when the object is brought

    if not 'Activeatemod' in store.mod_items: store.mod_items.append(Activeatemod)
    # This line adds the item into the mod_items list if its not in there already.


When it is brought, the buy function triggers and activates all the things indented under it.

This can therefore be used to trigger something... like a planet appearing by buying a map.

[/code]

So to modify Sunrider's Missiles to not take up ammo but increase energy cost, you could put:
Code: [Select]
    def buy():
        sunrider.weapons[2].ammo_use = 0 # 3rd weapon in sunrider.weapons list
        sunrider.weapons[2].energy_use = 50

The store is an incredibly flexible and usefull tool, an example of modifying a weapon is below

Code: (Example of modifying a weapon) [Select]
init python:

    class ModifyGG(StoreItem): # unique class ID
        def __init__(self):
            StoreItem.__init__(self) # same as class ID
            self.id = ModifyGG
            self.visibility_condition = 'bianca.weapon[1].energy_use == 60 and bianca.max_en >= 120' # visible if bianca max energy is greater than or equal to 120 and Bianca's 2nd weapon (gravity gun) has an energy cost of 60.
            self.display_name = 'UPRADE: GRAV GUN'
            self.cost = 800
            self.tooltip = 'This upgrade uses Bianca's upgraded reactor to channel excess power into her gravity gun'
            self.variable_name = None
            self.max_amt = 0
       
        def buy(self):
            bianca.weapon[1].energy_use = 40 # sets the energy_use attribute of weapon 2 in bianca.weapons to 40, thus removing itself from the store as its visibility condition is now not met.
           
    store.mod_items.append(ModifyGG) # adds the item to the store.
ChatButtons:

This is a modification of the dispatch label via a label hijack that is auto activated when the M52-54.rpy file is in the Sunrider/game folder.

It lets you add chat buttons to the sunrider map when certain conditions are met and is ideal for introduceing storys or letting the player talk to people at will.

Code: (Use:) [Select]
init python:
    chat_labels.append(['Condition','char','location','label'])

    #Example:

    chat_labels.append(['mission11_complete and ica_location == None and chi_location == None and Boostertalk == False','ica','lab','Rebuildbooster'])

Ok... This can look complicated (and it can be extreemly complicated) but it is fairly simple when broken down into bits.

chat_labels.append([]) # This adds an entery into the chat_labels list. it is a list as you can tell by the []
'condition' # This works the same as the planet visibility and mission visibility. (See below)
'Char' # This is the same shorthand as the text code we saw in the first few lines. It tells you what button to show and the variable to modify
'Location' # This is the location on the map to display the button
'Label' # This is the label to jump to.

The Locations are:

'captainsloft', 'sickbay', 'messhall', 'bridge', 'engineering', 'lab', 'hangar'

Conditions:

This is a bit tricky... When working out the conditions then you want:

1: Timeing variables (These can be your own, or they could be mission**_complete and such.
2: Character variables (so you dont overwrite any existing buttons, think ica_location == None)
3: Stopping variables (A variable set when you use the label so it ise't refreshed constantly)


The Ending

Well... If you have managed to read through this entire tutorial I am deeply impressed and if you actually managed to piece the mod together and it helped make a mod, I am ecstatic!

The complete mod code is here: (although it doesnít have a label. hijack because I donít know where you would be in the story and it doesn't have a chatbutton code)

Spoiler
Code: [Select]
init python: # init blocks run at the start of the game or on loading
   
    def Newsmod(): # Defines a function
        renpy.jump("News") # Python version of jump
       
       
    class Map(StoreItem): # needs to be a uniuqe name
        def __init__(self):
            StoreItem.__init__(self)
            self.id = 'ModMap' # needs to be uniuqe, I normaly use the class
            self.display_name = "Mystry Map" # name in store
            self.cost = 0            #cost
            self.tooltip = "Clicking me enables the News planet"
            self.visibility_condition = 'store.Hasmap == False'
           
        def buy(self):           
            store.Hasmap = True # Hasmap is a attribute of the store object
       
    if not hasattr(store,'Hasmap'): # This sets Hasmap to False if it dosn't exist
        store.Hasmap = False
        modmission = 0
        store.mod_items.append(Map) # This puts the Map item in store.mod_items

    Planet("ModPlanet", "planetwarp1", 1370, 450, "store.Hasmap == True")

label planetwarp1:
    image modbg2 = "Map/Farport.jpg"
    image modinfo2 = "Map/farport_info.png"
    $ modjumplabel = "News"
    jump missionselectionlabel

label missionselectionlabel: # You can call this whatever you want

    $ map_back = "planetback" #plug in the label to go back (below)
   
    if modmission == 0: # set a variable if you want the mission(s) to be available, you can have three missions visible at once but it dosent line up well
        $ galaxymission1 = True         #/ and is not suited to use with flexicode atm
        $ mission1 = "transit" # Jump to label for mission
        $ mission1_name = "Memory: Escape from Cera." #mission name

    else: # If the variable isent matched then these missions are used (all set to false atm)
        $ galaxymission1 = False
        $ mission1 = None
        $ mission1_name = None
        jump showmodplanet
       
label showmodplanet:       
    scene bg black
    show galaxymap:
        zoom 1 alpha 1
        parallel:
            ease 0.5 alpha 0
        parallel:
            ease 1 xpos -13710 ypos -5190  zoom 10
    show modbg2:
        zoom 0.0268041237113402
        xpos 1071 ypos 519 alpha 0
        parallel:
            ease 1.1 alpha 1
        parallel:
            ease 1 zoom 1 xpos 0 ypos -430
    pause 1
    show modinfo2:
        xpos 1098 ypos 200
    call screen map_travelto
    with dissolve

label planetback: # this is used to return from the planet map to the galaxy map
    hide modinfo
    scene bg black
    show modbg2:
        zoom 1
        xpos 1370 ypos 450 alpha 1
        parallel:
            ease 0.5 alpha 0
        parallel:
            ease 1 zoom 0.0268041237113402 xpos 1490 ypos 725
    show galaxymap:
        xpos -14900 ypos -7250 zoom 10 alpha 0
        parallel:
            ease 1.1 alpha 1
        parallel:
            ease 1 xpos 0 ypos 0 zoom 1
    pause 1
    call screen galaxymap_buttons
   
label transit:
   
    $ Random = renpy.random.randint(1,9) # sets random integer for below text.

    if Random == 1:       
        scene space back1 
    if Random == 2:       
        scene space back2 
    if Random == 3:       
        scene space back3 
    if Random == 4:       
        scene space back4 
    if Random == 5:       
        scene space back5 
    if Random == 6:       
        scene space back6 
    if Random == 7:       
        scene space back7 
    if Random == 8:       
        scene space back8 
    if Random == 9:       
        scene space back9 
                                                     
    show sunrider_warpout_standard:                   
        xpos 700 ypos 350                             
    with dissolve                                     
                                                     
    pause 1.0                                         
                                                     
    play sound "Sound/large_warpout.ogg"             
    show sunrider_warpout_standard_flash:             
        xpos 426 ypos 0 alpha 0                       
        linear 0.1 alpha 1                           
        linear 0.1 alpha 0                           
    show sunrider_warpout_standard out:               
        xpos 700 ypos 350                             
        ease 0.2 xpos 200 ypos 300 zoom 0             
                                                     
    pause 1.0                                         
                                                     
    scene modbg2:
        ypos 0
        ease 1.5 ypos -120   
    with dissolve                                   
    pause 1                                           
                                                     
    show sunrider_warpout_standard out:               
        xpos 2300 ypos 1200 zoom 2                   
        ease 0.2 xpos 1000 ypos 500 zoom 0.5         
    pause 0.2
    play sound "Sound/large_warpout.ogg"
    show cg_legionwarpin_missilefrigate_warpflash:   
        zoom 1.5 xpos 1550 ypos 750               
    show sunrider_warpout_standard
   
    pause 2.0
    $renpy.jump(modjumplabel)
   
label News: # The start of the News Block.

    $Modmission = 1
   
    show bg bridge
   
    show ava uniform altneutral angry with fade:
        xpos 0.5

    ava "Captain! The Alliance is broadcasting a report on the PACT advance and attack on Cera!" # defined speaker
    kay "They have to start mobilising soon, put it on the main screen."
    "" "Ava turns to the screen"
    show ava uniform altneutral angry:
    ava "It looks like their showing our escape..."   

    menu: # Menu block, everything is indented.
        ava "Do you want any popcorn?" # Is not a new block so displays normally
       
        "Yes": # Is a new block so this is a choice
            kay "Watching us almost get killed does make me hungery..." # Indented
            jump Popcorn # Jump to label Popcorn
       
        "No":
            kay "No thanks, just a diet coke."
            jump Nopopcorn
           
label Popcorn:
    "Ava passes you the popcorn and you settle into your seats as the lights dim"
    jump setupmission22
   
label Nopopcorn:
    "Ava shrugs and passes you the bottle. You had better watch those expensive command consoles!"
    "As you settle back into your chairs, the lights start to dim in preparation"
    jump setupmission22
   
label setupmission22:

    window hide
    $ BM.mission = '22'
    call mission22init
    jump battle_start

label mission22init:

    python:
        zoomlevel = 1
        enemy_ships = []
        destroyed_ships = []

        BM.xadj.value = 872
        BM.yadj.value = 370

        create_ship(MissileFrigate(),(11, 7))
        create_ship(PhoenixBoaster(),(11, 6))

        #sunrider.set_location(7, 7)

    $ PlayerTurnMusic = "music/Titan.ogg"
    $ EnemyTurnMusic = "music/Dusty_Universe.ogg"
    return

label mission22:

    $ BM.battle()
    if BM.battlemode == True:
        jump missionnews
    else:
        jump aftermission22

label after_mission22:
    "Newsperson" "So at least some of the Cera military are known to have suvived the PACT assault"
    kay "So... At least someone in Cera may know we survived"
    ava "But PACT are more than likely to come after us now we're in the public view."
    jump dispatch

Good Luck and Happy Modding

If there's anything someone thinks should be added, let me know!

Advanced Stuff:

Battle Modding:
Understanding Battles:
The entire player turn is a loop that continues until the player looses or it is ended by clicking the end turn button. At that point, it goes through the enemy_turn code before returning back to the player code.

This means when you play the battle, every click you make starts the loop again.

Therefore we need some way to distinguish between turns.
Turn Gates:
Code: ( Basic battle code) [Select]
label Mission99:

    $ BM.battle()
    if BM.battlemode == True:
        jump mission99
    else:
        jump aftermission99

If you add $turncount = 0 to the init label before the mission you can then do this:

Code: (Turn Gate:) [Select]
label mission99:
    if turncount != BM.turn_count:
        $ turncount = BM.turn_count

        if turncount == 1:
            do stuff

    python:
        if len(BM.ships) > shipcount: shipcount = len(BM.ships)
        if len(BM.ships) < shipcount:
            shipcount = len(BM.ships)
            renpy.say("Ava", "Ship destroyed")

    $ BM.battle()
    if BM.battlemode == True:
        jump mission99
    else:
        jump aftermission99

Anything in the turn-gate gets run once per turn, you can seperate turns by adding a check for what turn it is.

Anything outside of the turn-gate, like the second code block is run with every click of the mouse. This is good for checking the state of the battle and responding to it. In this case, Ava makes a comment about a ship being destroyed when the number of entries in BM.ships is less than it was.

(Assume shipcount is set to 0 or something in the battle-init label)

The M52-53._7.2_1.1.rpy file in functions zip allows you to have a function that runs at the start of every turn of missions. This is most usefull for global mods.
Showing images in battle:
Whilst there is no real difference in speaking in a battle (with the exception of speaking whilst in a python block), to display an image then you need to include a small bit of extra code.

Code: [Select]
label mission99:
    if turncount != BM.turn_count:
        $ turncount = BM.turn_count
       
        show kryska plugsuit alt neutral frown onlayer screens with dissolve
        kry "I AM SPEAKING IN SPACE"
        hide kryska onlayer screens with dissolve

Basically whenever you want an image to appear or dissapear in a battle, you need to specify onlayer screens.

Speaking and showing images in Python

This is all possible inside a python block, it is however slightly different.

Speaking:

There are two ways to do this, either useing the shorthand as normal or useing the renpy.say function. For most purposes you can use the shorthand (as it is actually a function) and just need to make a slight change in format.

Shorthand:

This is by far the easiet way to talk in a python block.

Code: (Shorthand) [Select]
python:
    ava("... Idiot")

Note that you need to include brackets because this is a function, and either single or double quotes to tell python that this is a string. Other than that it is very similar to the standard shorthand text we covered in the first two mins of this tutorial.

The other method is not normally requried but it can be usefull if you dont want to define a speaker and want a one-off character. It is usefull to know as it helps you understand renpy.equivilents with code you have already used.

Renpy say equivalent:

Functions are basicly small snippits of code that are recyclable, in short we have been useing disguised functions whenever we make a character speak. This is what they look like under the skin!

(have a look at: http://www.renpy.org/doc/html/statement_equivalents.html for more)

Code: (renpy say equivalent) [Select]
python:
    renpy.say("character","words") # This will display the name character saying words
    renpy.say("kryska","stuff")
    renpy.say(ava,"idiot") # The shorthands work as well, but why you would use them if their already set up, i'm not sure.

Its very easy to use this, but you must make sure you have the quotes.


Displaying images in python blocks:

Unlike displaying text, images can look quite complex to display. It follows the same patterns but there are extra variables and to get it to fade, you need to add another command.

Code: [Select]
python:
    renpy.show('imagename', at_list = [], layer = 'screens')
   
    renpy.hide('image', layer = 'screens')

This show command is harder to place, the image name can be either the physical location of the image, or it can be a defined image. the at_list variable is used for positioning and transforms. (see: http://www.renpy.org/doc/html/transforms.html)

These can be created but they are still a slightly clunky way to place an image.

Code: (Example:) [Select]
python:
    renpy.show('kryska plugshit altneutral frown' at_list = [topleft], layer = 'screens')
    renpy.with_statement(dissolve)
   
    renpy.hide('kryska', layer = 'screens')
    renpy.with_statement(wipedown)


This sets the image to appear at topleft (look at the link for the complete list) but also includes a new statement. If you don't use an at_list then it defaults to center.

renpy.with_statement(transition) is used when you want to add a transition to an image changing, think of it as the with dissolve we used earlier.

Code: (Complete example) [Select]

label mission99:
    python:
        if turncount != BM.turn_count:
            turncount = BM.turn_count
           
            if turncount == 2:
                renpy.show("ava uniform alt order mouthopen", at_list = [topleft], layer = 'screens')
                renpy.with_statement(wipeup)
               
                ava("Watch out for that ship!")
               
                renpy.show('ava uniform alt neutral mad', atlist = [topleft], layer = 'screens')
               
                renpy.say("Ava", "You need to pay attention!")
               
                renpy.hide('ava',layer = 'screens')
                renpy.with_statement(wipedown)
               
                create_cover((sunrider.location[0]+1,sunrider.location[1]))


This completed example checks the turn number, then if its the 2nd turn, shows ava shouting at the pilot, changing position and hides her, then a cover is made slightly away from the sunrider location.
Battle Maps

Placeing ships can be hard to visualise, and doing so while testing for balance can be frustrating so the Toolkit function in functions.zip, when dropped in Sunrider/game will create a small + button on the top of the skirmish map.

When you have placed the ships you want, then click the + sign and it will make a file with a basic battle printout that you can use to test your battle for balancing.

As of the moment it dosn't make covers and it dosn't separate player ships into its own paragraph but at some point I will update it to do so.

Code: (Example output) [Select]
label setupmission**:

    window hide
    $ BM.mission = '**'
    call mission**init
    jump battle_start

label mission**init:

    python:
        zoomlevel = 1
        enemy_ships = []
        destroyed_ships = []

        BM.xadj.value = 872
        BM.yadj.value = 370

        create_ship(PactMook(),(8, 3))
        create_ship(PactMook(),(10, 3))
        seraphim.set_location(2, 4)
        blackjack.set_location(6, 4)
        create_ship(PactMook(),(8, 4))
        create_ship(PactAssaultCarrier(),(16, 4))
        bianca.set_location(5, 5)
        create_ship(MissileFrigate(),(12, 5))
        create_ship(PactAssaultCarrier(),(16, 5))
        liberty.set_location(4, 6)
        sunrider.set_location(5, 6)
        phoenix.set_location(7, 6)
        create_ship(PactMook(),(8, 6))
        paladin.set_location(5, 7)
        create_ship(MissileFrigate(),(10, 7))
        create_ship(MissileFrigate(),(11, 7))
        create_ship(PactBattleship(),(13, 8))

    $ PlayerTurnMusic = "music/Titan.ogg"
    $ EnemyTurnMusic = "music/Dusty_Universe.ogg"
    return

label mission**:

    $ BM.battle()
    if BM.battlemode == True:
        jump mission**
    else:
        jump aftermission**

Just change the ** to your mission variable and you can drop it right into the battle. This will not pick up non-core player units like mercenaries. This will likely save you hours of work, but as a side effect it also enables all enemy_ships for the skirmish placement and SHOULD NOT be included with your module[/color]
Ending Battles
Battles are won when BM.you_win() is called. This can be by destroying a ship flagged as Boss, destroying all enemy ships or by code directly calling BM.you_win()

Battles are lost by calling BM.you_loose(), this is called when a critical ship is killed or all player ships are destroyed.

As modifying these will affect the entire game, not just a specific mod. I have created replacement functions that will let you schedule functions for either winning or losing and run these either with or instead of the originals.

To use this then include the three M52 files from functions.zip in your mod.

Include the following code in an init block after -10:
Code: (Useage) [Select]
init python:
    init_win_funcs.append([BM MISSION TO USE, FUNCTION TO USE])
#or
    init_loss_funcs.append([BM MISSION TO USE, FUNCTION TO USE])
   
#Example:
    init_loss_funcs.append([60,crashships])


When using this remember that to maintain flexibility, if you do not direct the function out of the battle, it will carry on to either the loss or victory screen.

To escape this then use something like this:


Code: (escape battle) [Select]
init python:
    def function():
        #do stuff
        clean_battle_exit() # this resets the battle map for the next fight
        renpy.jump('labelname')

Programmed Battle phases
This may be useful to coders for understanding how to implement complex things into battles, but remember if done without returning them to their original states, modifying this will cause other battles to be changed as well.

Battle Start
This is a label that is called, it is used to set the battle up and to store variables for restarting a battle.

Code: [Select]
label battle_start:
    play music PlayerTurnMusic # starts up the music
    python:
        BM.battlestart.player_ships = store.player_ships[:]                 #|
        for ship in BM.battlestart.player_ships:                            #|
            ship.battlestart_location = ship.location                       #|
        BM.battlestart.enemy_ships = deepcopy(store.enemy_ships)            #| stores variables
        BM.battlestart.covers = deepcopy(BM.covers)                         #|
        BM.battlestart.sunrider_rockets = sunrider.rockets                  #|
        BM.battlestart.sunrider_repair_drones = sunrider.repair_drones      #|
        BM.battlestart.cmd = BM.cmd                                         #|
        BM.stopAI = False
        BM.order_used = False
        BM.enemy_vanguard_path = []
        BM.player_vanguard_path = []
        BM.active_strategy = [None,0]
        renpy.take_screenshot()             #| Autosaves
        renpy.save('beginturn')             #|
        if BM.show_tooltips:
            renpy.show_screen('tooltips')
        # BM.xadj.value = 872
        # BM.yadj.value = 370
        for ship in player_ships:           #|
            ship.hp = ship.max_hp           #| Refreshes player ships
            ship.en = ship.max_en           #|
        # renpy.show_screen('mousefollow')
        store.zoomlevel = 0.65
        BM.show_grid = False
        sort_ship_list()
        BM.start()              #| Starts the battle loop


    return

BM.start() (L950 classes.rpy)
This is the bit of code that checks if the formation picker is used and then shows the screens. BM.editableformations is the function that chooses and could be modified, but its of limited interest for modding.
Code: [Select]
        def start(self):
            self.battle_log_insert(['system'], "-------------BATTLE START-------------")
            BM.player_ai = False
            battlemode() #stop scrollback and set BM.battlemode = True
            update_stats()  #used to update some attributes like armour and shields
            renpy.show_screen('battle_screen')

            #new formation feature (only after mission 12 for now)
            if self.editableformations(): # if BM.mission > 12 and not string
                self.phase = 'formation'

                for ship in player_ships:
                    if ship.location != None:
                        set_cell_available(ship.location)
                        ship.location = None

                renpy.show_screen('player_unit_pool_collapsed')
                renpy.show_screen('player_unit_pool')
                BM.selectedmode = False #failsafe
                BM.targetmode = False
                BM.selected = None #the selected unit doesn't show up in the pool
                self.formation_phase()
            else:
                self.jumptomission()
formation_phase is the handling function of placing ships and there's nothing really to mod there either.

jumptomission() just jumps you to your mission label. This is why you should always use the mission format given earlier in the tutorial.

The mission label scrolls through your code and then hits BM.battle where it starts to loop until you end your turn.

BM.battle() (981 classes.rpy)
This is getting more interesting! This function checks what the player clicked and then sends a value to BM.dispatch_handler(). It then checks for loss or win and if either of those returns true, they run the you_win and you_lose functions.
After that it bounces you back to your mission label
Code: [Select]
        def battle(self):
            for ship in player_ships:
                if not hasattr(ship, 'blbl'):        #| If they dont have the attr blbl
                    ship.blbl = ship.lbl             #| sets blbl to lbl for display

            if self.player_ai:              #|
                self.player_AI()            #| player can be set to AI, could well
                self.toggle_player_ai()     #| be some interesting mod stuff here...
                self.result = 'endturn'     #| NPC controled ships independant of players?
            else:
                #battle_screen should be shown, and ui.interact waits for your input. 'result' stores the value return from the Return actionable in the screen
                self.result = ui.interact()

            if store.Difficulty < self.lowest_difficulty:
                self.lowest_difficulty = store.Difficulty

            self.just_moved = False #this sets it so you can no longer take back your move
            renpy.hide_screen('game_over_gimmick') #disables the screensaver gimmick

            if self.stopAI and sunrider.hp < 0:  #some failsafe checking. stopAI functions like an emergency stop for AI code
                renpy.jump('sunrider_destroyed')
            if hasattr(store,'mochi'):
                if hasattr(mochi,'hp'):
                    if mochi.hp < 0 and mochi in player_ships:
                        renpy.jump('sunrider_destroyed')

            #sanity check
            for ship in self.ships:
                if ship.hp <= 0:
                    destroyed_ships.append(ship)
                    if ship in player_ships:
                        player_ships.remove(ship)
                    if ship in enemy_ships:
                        enemy_ships.remove(ship)
                    if ship in self.ships:
                        self.ships.remove(ship)

            self.dispatch_handler(self.result)()

            self.check_for_loss()
            self.check_for_win()
            return

Dispatches (classes 187+
The dispatch_handler function checks for the result of ui.interact in the previous function and then works out what to do with it.

This is done by checking it against the dispatcher returns list in classes.rpy lines 95-129. When it matches up the response then it knows what to do. Dispatcher locations are classes.rpy, lines 200-615.

These would be ideal bits to mod but (sorry to say this again you should remember these will affect all battles). It should be possible to add your own dispatchers and dispatch triggers. look in the custom screens rpy file to see more. There is unfortunately too much code to include it here.

End player turn:
This is done by clicking a displayable set in custom screens, it causes the battle_end_turn function to run which in turn calls the BM.end_player_turn function after clearing the selected ship.

Most of what this function does is set the AI to work.  It ticks the turn_count up a bit and resets ship flak

Using M52-53 then you can set a function to run at the end of the player turn (so pretty much on the enemy turn) if the BM.mission matches the requirements.

Code: (end_player_turn (clases line 1054)) [Select]
        def end_player_turn(self):
            self.battle_log_insert(['system'], "---------Player turn end---------")
            self.battle_log_trimm()
            renpy.hide_screen('commands')
            self.selected = None #some sanity checking
            self.target = None
            self.moving = False
            self.selectedmode = False
            self.targetingmode = False
            self.active_weapon = None
            self.weaponhover = None
            self.turn_count += 1
            renpy.music.play(EnemyTurnMusic)
            renpy.call_in_new_context('endofturn')

            for ship in self.ships:
                ship.flak_effectiveness = 100

            self.enemy_AI() #call the AI to take over
            self.battle_log_insert(['system'], "---------{0} turn end---------".format(self.phase))
            self.battle_log_trimm()
             ##I have NO idea why this dumb workaround is needed, but the destroy() method -somehow- doesn't want to jump to this label sometimes.
            if sunrider.hp < 0:
                renpy.jump('sunrider_destroyed')

            for ship in self.ships:
                ship.flak_effectiveness = 100
                ship.getting_curse = False #failsafes
                ship.getting_buff = False
            for ship in player_ships:
                ship.en = ship.max_en * (100 + ship.modifiers['energy regen'][0] ) / 100
                if ship.en < 0: ship.en = 0
            self.active_weapon = None
            self.targetingmode = False
            self.target = None
            self.selected = None
            self.selectedmode = False
            self.order_used = False
            self.moving = False

            #run the end of turn callbacks
            if BM.end_turn_callbacks != []:
                for callback in BM.end_turn_callbacks:
                    callback()

            if self.battlemode:
                renpy.music.play(PlayerTurnMusic)
                renpy.call_in_new_context('endofturn')
            renpy.take_screenshot()

            # I've sometimes been getting this error for some silly reason:
            # WindowsError: [Error 183] Cannot create a file when that file already exists
            # may just be me, but to be safe I'll put a catch here
            try:
                renpy.save('beginturn')
            except:
                pass
At the end of the enemy's turn it jumps to endofturn label and updates buffs/curses. Now, finally the game makes its way back to the player turn and starts looping through BM.battle/dispatchers and the mission label.

Failed Missions:
If you fail then you are booted to the tryagain label, this resets certain variables stored by the mission at the start and offers you the chance to load a game. If you choose to restart then you are bounced back to battle_start.

The m52 file lets you store variables and run functions at battle-start and changes the tryagain label to Try2 so you can reset other variables, not just the ones auto-reset in this label.

Code: [Select]
label tryagain:
    hide badend
    $ clean_battle_exit(True)
    python:
        store.battle1_check1 = False
        store.battle2_check1 = False
        store.battle2_check2 = False
        store.battle_check1 = False
        i = 1
        while True:
            if hasattr(store, 'check{}'.format(i)):
                setattr(store, 'check{}'.format(i), False)
                i += 1
            else:
                break
       
        try:
            store.destroyed_ships = []
            store.player_ships = BM.battlestart.player_ships
            for ship in store.player_ships:
                ship.missiles = ship.max_missiles
                ship.location = ship.battlestart_location
            sunrider.rockets = BM.battlestart.sunrider_rockets
            sunrider.repair_drones = BM.battlestart.sunrider_repair_drones
            BM.cmd = BM.battlestart.cmd
            BM.turn_count = 1
           
            store.enemy_ships = BM.battlestart.enemy_ships
            for ship in store.enemy_ships:
                if isinstance(ship, Havoc):
                    store.havoc = ship
           
            BM.covers = BM.battlestart.covers
            for cover in BM.covers:
                cover.hp = cover.max_hp
        except:
            renpy.jump('tryagain_old')
        BM.ships = []
        for ship in store.player_ships:
            BM.ships.append(ship)
        for ship in store.enemy_ships:
            BM.ships.append(ship)
       
        BM.grid = []
        for a in range(GRID_SIZE[0]):
            BM.grid.append([False]*GRID_SIZE[1])
        for ship in BM.ships:
            if ship.location == None:
                continue
            x, y = ship.location
            BM.grid[x - 1][y - 1] = True
    jump battle_start
    return
If you Win:
When you win, battle_end is called. This ends the battle loop and resets BM variables for selected ships. It then shows the victory screen and displays ships destroyed in the battle_end function. This would be simple to mod for missions where it wasn't so clear if victory had been achieved, you wanted to modify money or you wanted to put your own spin on captured ships.
Code: [Select]
        def battle_end(self, lost = False):
            """ending the battle - reset values for next battle"""
            self.battlemode = False #this ends the battle loop
            if self.selected != None: self.unselect_ship(self.selected)
            self.targetingmode = False
            self.vanguardtarget = False
            self.weaponhover = None
            self.hovered = None
            BM.enemy_vanguard_path = []
            renpy.hide_screen('tooltips')
            BM.phase = 'Player'

            if store.Difficulty < self.lowest_difficulty:
                self.lowest_difficulty = store.Difficulty

            if not lost:
                #show the victory screen
                renpy.music.stop()
                renpy.music.play('Music/Posthumus_Regium_Finale.ogg', loop = False)
                renpy.hide_screen('commands')
                self.draggable = False
                renpy.show_screen('victory')
                renpy.pause(3.0)
                renpy.hide_screen('victory')

                store.repair_cost = 0
                store.total_money = 0
                store.boss_killed = False
                store.surrender_bonus = 0
                for ship in destroyed_ships:
                    if ship.faction == 'Player':
                        store.repair_cost += int(ship.max_hp * 0.2)
                    else:
                        if ship.boss: store.boss_killed = True #check if a boss was killed
                        store.total_money += ship.money_reward

                if store.boss_killed:
                    for ship in enemy_ships:
                        if ship.hp > 0:
                            store.surrender_bonus += ship.money_reward / 2

                for ship in player_ships:
                    store.repair_cost += int((ship.max_hp - ship.hp)*0.1)

                store.net_gain = int(store.total_money + store.surrender_bonus - store.repair_cost)

                #SPACE WHALE TAX!
                if store.Difficulty == 5:
                    store.net_gain *= 0.8

                self.money += int(net_gain)

                #Captain and higher difficulties reduce the total CP you get per battle.
                difficulty_penalty = store.Difficulty - 1
                if difficulty_penalty < 0: difficulty_penalty = 0

                self.cmd += int((net_gain*10)/(BM.turn_count+difficulty_penalty))

                renpy.show_screen('victory2')
                renpy.pause(1)
                renpy.hide_screen('victory2')
                self.draggable = True

            self.turn_count = 1
            self.active_strategy = [None,0]
            self.ships = []
            self.selectedmode = False
            self.battle_log = []
            renpy.hide_screen("battle_log")

            VNmode() #return to visual novel mode. this mostly just restored scrolling rollback
            for ship in destroyed_ships:
                if self.mission == 'skirmish' or (ship.faction == 'Player' and not ship.mercenary):
                    player_ships.append(ship)
                    self.ships.append(ship)
            for ship in player_ships:
                self.ships.append(ship)
            for ship in player_ships:
                ship.en = ship.max_en
                ship.hp = ship.max_hp
                ship.hate = 100
                ship.total_damage = 0
                ship.total_missile_damage = 0
                ship.total_kinetic_damage = 0
                ship.total_energy_damage = 0
                ship.missiles = ship.max_missiles
                ship.location = None #this helps if you add new ships but don't know the current location of the existing ones.
                for modifier in ship.modifiers:
                    ship.modifiers[modifier] = [0,0]

            #reset the entire grid to empty and BM.ships with only the player_ships list
            clean_grid()
            self.covers = []
            renpy.hide_screen('battle_screen')
            renpy.hide_screen('commands')

            renpy.block_rollback()

Createing Ships:
To Come
Createing Weapons
To Come
Battle MapMaker:
To Come

Expanded Mod Functionality:
To Come

Edit - 18-8-15: Added battle modding section and update Functions.zip to include a fixed toolkit.rpy and the ability to add runnable functions to the you_win and you_loose functions
Edit - 17-9-15: Modified the M52 Functions file

« Last Edit: September 17, 2015, 10:05:29 pm by The Bigfoot »
I make spaceships! - http://innomenpro.com/forums/index.php?topic=1366.0
Massive Modding tutorial here: http://innomenpro.com/forums/index.php?topic=1251.0
New Award: John Titor [Sep 17, 2015, 10:16:07 PM]:   BigFoot is the official Evil Genius fro mteh forum

Offline Endershadow

Re: [Tutorial] Story mod makeing with explained examples
« Reply #1 on: May 12, 2015, 08:43:24 am »
This is a good tutorial. I couldn't have made a better one myself.

Please be careful not to fall into any holes in reality caused by my reality hole generator. If you do fall in though, I can assure you that there will be no adverse side effects except for the following: instantaneous mutation, loss of 1 or more vital organs, turning into banana pudding, amnesia, loss/growth of 1 or more limbs, additional vital organs, loss of DNA, insanity, hallucinations, destruction of the fabric of space and time, redundancy, redundancy, a craving for apples, the ability to see shinigami, for females: an erection lasting more than 4 hours, for males: extremely painful menstrual cramps, testicular inversion, gender swapping, the urge to watch my little pony, and/or death

Offline The Nothing

Re: [Tutorial] Story mod makeing with explained examples
« Reply #2 on: May 12, 2015, 03:28:23 pm »
Thanks for your work, this is well explained, and I hope it will help a lot of new ren'py programers to do cool stuff with Sunrider.

Offline The Bigfoot

Re: [Tutorial] Story mod makeing with explained examples
« Reply #3 on: May 13, 2015, 09:43:46 pm »
Thanks for the good vibes! ;D

I have added a section on variables and makeing your own ships and weapons but its not something I have done much on so if anyone thinks of something that should go in there, let me know!
I make spaceships! - http://innomenpro.com/forums/index.php?topic=1366.0
Massive Modding tutorial here: http://innomenpro.com/forums/index.php?topic=1251.0
New Award: John Titor [Sep 17, 2015, 10:16:07 PM]:   BigFoot is the official Evil Genius fro mteh forum

Offline Ziktofel

Re: [Tutorial] Story mod makeing with explained examples
« Reply #4 on: May 15, 2015, 08:27:13 am »
stick it!

Offline Samu-kun

Re: [Tutorial] Story mod makeing with explained examples
« Reply #5 on: May 15, 2015, 04:47:03 pm »
Bunny day.

Offline Shirley

IT'S A CONSPIRACY!! Because you all suck and need to lighten up  Me In a Nutshell
KomiTsuku [Sep 22, 2015, 03:43:44 PM]:But tentacle monsters are our friends. BITE ME UNIVERSE [Oct 16, 2016, 09:21:04 pm]:   because one type of forbidden love just isn't enough Afro-Kun [Facebook Nov 21, 2015, 22:40:01 PM]: "I WAS JUST HANDED TEA AND THEN; Friend: "I put the tea in "NTR"!" FRIEND PLS"

Offline Veniczar Derle

Re: [Tutorial] Story mod makeing with explained examples
« Reply #7 on: May 16, 2015, 01:28:38 am »
Thank for the label hijacking trick. I will use it on my mod.
I am not very fluent in English. I beg you to forgive me for that.
My avatar come from here http://budokai01.deviantart.com/art/Infinite-Space-Wallpaper-175541546/.

Offline The Bigfoot

Re: [Tutorial] Story mod makeing with explained examples
« Reply #8 on: May 15, 2015, 08:05:46 pm »
Thank for the label hijacking trick. I will use it on my mod.

Fantastic, I look forward to trying it! May be worthwhile to say what labels you override for compatibility.

Its unlikely to be a problem but in the case of two override conflicts then the one which is run last would take priority. init blocks are ordered according to priority so init 1: would be ran after init:
I make spaceships! - http://innomenpro.com/forums/index.php?topic=1366.0
Massive Modding tutorial here: http://innomenpro.com/forums/index.php?topic=1251.0
New Award: John Titor [Sep 17, 2015, 10:16:07 PM]:   BigFoot is the official Evil Genius fro mteh forum

Offline Veniczar Derle

Re: [Tutorial] Story mod makeing with explained examples
« Reply #9 on: May 16, 2015, 04:27:57 pm »
It is a VN mod for Sunrider Academy that lets you see the VN without having to play the game (I am not very good at stat management games and I spent most of my time uninterested in what happened at the screen. the game is surely very good, but I am not the one that can enjoy it.).

The class selection label is redirected to a label that let the player choose which route he or she wants to see the label.
(The player will still have to add a "jump class_selection" at the end of the start label in script.rpy.)
I am now testing to see if I have all the label in the right route and order.
Chigara route is almost ready, some scene are not right but the labels seem fine.
I am not very fluent in English. I beg you to forgive me for that.
My avatar come from here http://budokai01.deviantart.com/art/Infinite-Space-Wallpaper-175541546/.

Offline The Bigfoot

Re: [Tutorial] Story mod makeing with explained examples
« Reply #10 on: May 22, 2015, 12:22:13 am »
Big update... The Mod Toolkit (Battle edition)

The big is a modification to skirmish mode that lets you save your ship's positions into a file that you can copy-paste into a mod script for a battle.
This can save a vast amount of time and makes balanceing very easy and much much less time consumeing!

Code: (Example output:) [Select]
label setupmission**:

    window hide
    $ BM.mission = '**'
    call mission**init
    jump battle_start

label mission**init:

    python:
        zoomlevel = 1
        enemy_ships = []
        destroyed_ships = []

        BM.xadj.value = 872
        BM.yadj.value = 370

        create_ship(PactElite(),(13, 5))
        create_ship(PactElite(),(12, 6))
        create_ship(PactAssaultCarrier(),(13, 6))
        create_ship(PactBattleship(),(10, 9))
        create_ship(PactBattleship(),(10, 8))
        create_ship(PactMook(),(9, 8))
        create_ship(PactMook(),(9, 9))
        create_ship(PactMook(),(8, 10))
        create_ship(PactMook(),(10, 7))
        create_ship(PactMook(),(11, 7))
        create_ship(PactMook(),(11, 8))
        create_ship(PirateBase(),(4, 11))
        create_ship(PirateIronhog(),(3, 10))
        create_ship(PirateDestroyer(),(4, 9))
        create_ship(PirateDestroyer(),(4, 10))

        sunrider.set_location(5, 6)
        bianca.set_location(4, 7)
        seraphim.set_location(3, 5)
        paladin.set_location(8, 7)
        liberty.set_location(6, 5)
        black jack.set_location(8, 6)
        phoenix.set_location(8, 5)

    $ PlayerTurnMusic = "music/Titan.ogg"
    $ EnemyTurnMusic = "music/Dusty_Universe.ogg"
    return

label mission**:

    $ BM.battle()
    if BM.battlemode == True:
        jump mission**
    else:
        jump aftermission**

The small is a group of functions that can be used to script battles quickly.

findships() # finds ships that match all variables and optionally runs a function on them (variables are Name, List, Type, Faction, distance from point)

dmgships() # uses findships to cause damage to matching ships and destroys them if hp < 0

countships() # returns intiger of ships that match criteria

modships() # applies a modifier or list of modifiers to ships that match criteria

capships() # !still in progress! Flips ship teams and modifies them so they can be used by player without crashing (melee and support still have problems) Can be used on player ships.


Example with gripping story!

Code: ( This hasent actually been tested) [Select]
python:
    if nothappenedyet == 0:
        if countships(Name='Liberty', Dist = (1,(5,5)) == 1: # if liberty is within 1 hex of whater is in 5,5

            renpy.say("chi","Scanning target")
            renpy.say("ava","The device has reacted releaseing mind control things!")

            capships(Faction = 'Player', Dist (3,(5,5)) # converts any player ships within 3 from 5,5 to PACT

            renpy.say("ava","Now its inexplicably blowing up PACT cruisers!")
            renpy.say("kay","We cant target the PACT Mooks in this debris field!")

            dmgships(10000, Type = 'Cruiser', List = enemy_ships) # does 10K dmg to cruiser enemys in enemy_ships
            modships([['stealth',[100,5]]], Name = 'PACT Mook') # grants PACT Mooks stealth for 5 turns
            nothappenedyet = 1

Full documentation inside the (very messy) Toolkit file.
« Last Edit: May 22, 2015, 05:59:17 pm by The Bigfoot »
I make spaceships! - http://innomenpro.com/forums/index.php?topic=1366.0
Massive Modding tutorial here: http://innomenpro.com/forums/index.php?topic=1251.0
New Award: John Titor [Sep 17, 2015, 10:16:07 PM]:   BigFoot is the official Evil Genius fro mteh forum

Offline Veniczar Derle

Re: [Tutorial] Story mod makeing with explained examples
« Reply #11 on: May 25, 2015, 07:54:52 pm »
Very useful, I was wondering if something like that already exists.
It will make creating battle much easier. 
I am not very fluent in English. I beg you to forgive me for that.
My avatar come from here http://budokai01.deviantart.com/art/Infinite-Space-Wallpaper-175541546/.

Offline Carthienes

Re: [Tutorial][7.2] Story mod makeing with explained examples
« Reply #12 on: June 19, 2016, 03:51:14 pm »
I just want to say: Thankyou

I had no idea how to mod Renpy, but after the debacle that was Liberation Day, I was determined to find some way to edit out the advert at the end of Mask of Arcadius and replace it with a much more palatable "The End". Crude, unsatisfying, and infinitely more palatable. I had no idea how feasible that would be, but neither did I care.

Thanks to you, I was able to do much more than that. I am currently working on a Mod that will replace the advert with a bit more than "The End" - a short, non-interactive view of the future based on a few variables pulled from the play-through. Not quite sure how far I am going to take it, though.

I am still working on the item and planet placement, but in the mean time this is my version of your mod - there does seem to be one major bug:
(I made a few tweaks to the mod code as written. Mainly grammar that were irritating me, but for some reason 'show ava uniform altneutral angry with move:' turned her into a grey box, until I replaced it with just 'show ava with move:')
Code: [Select]
#This is the Tutorial Mod created by The Bigfoot. All original credit goes to him.

init python: # init blocks run at the start of the game or on loading
    config.label_overrides['warptotydaria'] = 'warptotydaria_mod'
    config.label_overrides['aftercredits7'] = 'aftercredits_mod'
   
    if not hasattr(store, 'check_news'):
        store.check_news = True
   
    def Newsmod(): #Defines a function
        renpy.jump("News") # Python version of a jump

label warptotydaria_mod:    #Copied Verbaitim from overridden warptotydaria

    $ warpto_occupiedcera = True
    $ warpto_tydaria = True
   
    #if not store.check_news:
        #$ newsrun = True

    hide screen deck1

    scene bg bridge
    show ava uniform alt neutral neutral
    with dissolve

    window show

    ava "You can use the starmap at the center of the bridge to plot our course."

    hide ava with dissolve

    menu:
        "Access galaxy map.":
            jump galaxymap

        "Return to map.":
            jump dispatch
       
        "Check news" if store.check_news: #only show if not ran
            $ store.check_news = False
            jump News


label News: # The start of the news block
   
    scene bg bridge # show background bridge
   
    show ava uniform altneutral angry with fade: # with fade is how it enters
        xpos 0.5 # X axis location for middle of the screen
   
    ava "Captain! The Alliance is broadcasting a report on the PACT advance and the attack on Cera!" # Defined Speaker
    "Kayto" "They have to start mobilising soon, put it on the main screen" # Named text
    "Ava turns to the screen" # With no speaker quotes, this is just placed on the screen.
   
    #show ava uniform altnuetral angry with move: # moves rather than places, angry because she normally is!
    show ava with move: #It seems that specifying ava stays the same sprite breaks renpy
        xpos 0.3
   
    ava "It looks like they're showing our escape..."
   
    menu: #Menu block, everything is indented
       
        ava "Do you want any popcorn?" # Is not a new block so displays normally
       
        "Yes": # [u]IS[/u] a new block so this is a choice
            kay "Watching us almost get killed does make me hungry..." # Indented. kay is shorthand for Kayto speaking
            jump Popcorn # Jump to label Popcorn
       
        "No":
            kay "No thanks, just a diet coke."
            jump Nopopcorn


label Popcorn:
    "Ava passes you the popcorn and you settle into you seats as the lights dim"
    jump setupmissionnews
   
label Nopopcorn:
    "Ava shrugs and passes you the bottle. You had better watch those expensive command consoles!"
    "As you settle back into your chairs, the lights start to dim in preparation"
    jump setupmissionnews


label setupmissionnews:
    window hide # Hides message bar
    $ BM.mission = 'news' # tells battle manager mission name
    call missionnewsinit # sets mission variables
    jump battle_start # starts the battle
   
label missionnewsinit:
   
    python:                     #/
        zoomlevel = 1           #|This code is used in every mission initIt just cleans the battlefield
        enemy_ships = []        #|
        destroyed_ships = []    #\
       
        sunrider.set_location(1,1)  #comment out ships that you haven't met yet
        #blackjack.set_location(2,2)    #these are the coordinates to place the ship
        #liberty.set_location(3,3)
        #phoenix.location = (4,4)    #set location to None to use in battle, there is a better way to do this but its simple for now
        #bianca.set_location(5,5)
        #seraphim.set_location(6,6)
        #paladin.set_location(7,7)
       
        BM.xadj.value = 872 # set battle view, this does not really need to be changed erver
        BM.yadj.value = 370
       
        create_ship(MissileFrigate(), (13,5)) # This creates a ship with default armament
        create_ship(MissileFrigate(), (15,5), [PactFrigateMissile()]) # creates a modified ship
        enemy_ships[-1].max_en=80
        enemy_ships[-1].boss = False
        enemy_ships[-1].Faction ='PACT'
        enemy_ships[-1].name = 'Damaged Missile Frigate'
        enemy_ships[-1].max_hp = 100
        enemy_ships[-1].hp = 30
       
        create_cover((8,4)) #creates cover
        create_cover((1,1))
   
    $ PlayerTurnMusic = "music/Titan.ogg" #set music
    $ EnemyTurnMusic = "music/Dusty_Universe.ogg"
    return # goes back to setupmissionnews where it was called

label missionnews: # This is the actual mission, very simple
   
    $ BM.battle() # Calls your turn and waits for a response
    if BM.battlemode == True: #When set to false battle ends
        jump missionnews # Loop Back
    else:
        jump after_missionnews

label after_missionnews:
    "Newsperson" "So at least some of the Cera military are known to have survived the PACT assault"
    kay "So... At lease someone in Cera may know we survived"
    ava "But PACT are more likely to come after us now we're in the public view"
    jump dispatch # shipmap
   
   
label aftercredits_mod:
    "The prototypes' words rang hollow, for Captain Shields and the crew of the Sunrider lived happily ever after. The End."
   
    #From here is copied directly from overridden aftercredits7 label
    play sound "sound/drum.ogg"
   
    $ renpy.pause(1.0)

    stop music fadeout 1.5
    scene white with dissolvelong

    play sound "sound/drumroll.ogg"

    "And now... The results of our great waifu war!"
    "And the winner is..."

    show poll6:
        xalign 0.5 yalign 0.5
    with dissolve
   
    pause
   
    "... ... ..."
   
    show ava hs armscrossed pout with dissolve
   
    ava "... ... ..."
   
    show ava hs handhair blush with dissolve
   
    ava "Idiot."
   
    $ renpy.full_restart()
    return
« Last Edit: June 23, 2016, 08:01:30 am by Carthienes »

Offline The Bigfoot

Re: [Tutorial][7.2] Story mod makeing with explained examples
« Reply #13 on: June 19, 2016, 10:39:23 pm »
I just want to say: Thankyou

I had no idea how to mod Renpy, but after the debacle that was Liberation Day, I was determined to find some way to edit out the advert at the end of Mask of Arcadius and replace it with a much more palatable "The End". Crude, unsatisfying, and infinitely more palatable. I had no idea how feasible that would be, but neither did I care.

Thanks to you, I was able to do much more than that. I am currently working on a Mod that will replace the advert with a bit more than "The End" - a short, non-interactive view of the future based on a few variables pulled from the play-through. Not quite sure how far I am going to take it, though.

I am still working on the item and planet placement, but in the mean time this is my version of your mod - there does seem to be one major bug:
Spoiler
I made a few tweaks to the mod code as written. Mainly grammar that were irritating me, but for some reason 'show ava uniform altneutral angry with move:' turned her into a grey box, until I replaced it with just 'show ava with move:'
Code: [Select]
#This is the Tutorial Mod created by The Bigfoot. All original credit goes to him.

init python: # init blocks run at the start of the game or on loading
    config.label_overrides['warptotydaria'] = 'warptotydaria_mod'
    config.label_overrides['aftercredits7'] = 'aftercredits_mod'
   
    if not hasattr(store, 'check_news'):
        store.check_news = True
   
    def Newsmod(): #Defines a function
        renpy.jump("News") # Python version of a jump

label warptotydaria_mod:    #Copied Verbaitim from overridden warptotydaria

    $ warpto_occupiedcera = True
    $ warpto_tydaria = True
   
    #if not store.check_news:
        #$ newsrun = True

    hide screen deck1

    scene bg bridge
    show ava uniform alt neutral neutral
    with dissolve

    window show

    ava "You can use the starmap at the center of the bridge to plot our course."

    hide ava with dissolve

    menu:
        "Access galaxy map.":
            jump galaxymap

        "Return to map.":
            jump dispatch
       
        "Check news" if store.check_news: #only show if not ran
            $ store.check_news = False
            jump News


label News: # The start of the news block
   
    scene bg bridge # show background bridge
   
    show ava uniform altneutral angry with fade: # with fade is how it enters
        xpos 0.5 # X axis location for middle of the screen
   
    ava "Captain! The Alliance is broadcasting a report on the PACT advance and the attack on Cera!" # Defined Speaker
    "Kayto" "They have to start mobilising soon, put it on the main screen" # Named text
    "Ava turns to the screen" # With no speaker quotes, this is just placed on the screen.
   
    #show ava uniform altnuetral angry with move: # moves rather than places, angry because she normally is!
    show ava with move: #It seems that specifying ava stays the same sprite breaks renpy
        xpos 0.3
   
    ava "It looks like they're showing our escape..."
   
    menu: #Menu block, everything is indented
       
        ava "Do you want any popcorn?" # Is not a new block so displays normally
       
        "Yes": # [u]IS[/u] a new block so this is a choice
            kay "Watching us almost get killed does make me hungry..." # Indented. kay is shorthand for Kayto speaking
            jump Popcorn # Jump to label Popcorn
       
        "No":
            kay "No thanks, just a diet coke."
            jump Nopopcorn


label Popcorn:
    "Ava passes you the popcorn and you settle into you seats as the lights dim"
    jump setupmissionnews
   
label Nopopcorn:
    "Ava shrugs and passes you the bottle. You had better watch those expensive command consoles!"
    "As you settle back into your chairs, the lights start to dim in preparation"
    jump setupmissionnews


label setupmissionnews:
    window hide # Hides message bar
    $ BM.mission = 'news' # tells battle manager mission name
    call missionnewsinit # sets mission variables
    jump battle_start # starts the battle
   
label missionnewsinit:
   
    python:                     #/
        zoomlevel = 1           #|This code is used in every mission initIt just cleans the battlefield
        enemy_ships = []        #|
        destroyed_ships = []    #\
       
        sunrider.set_location(1,1)  #comment out ships that you haven't met yet
        #blackjack.set_location(2,2)    #these are the coordinates to place the ship
        #liberty.set_location(3,3)
        #phoenix.location = (4,4)    #set location to None to use in battle, there is a better way to do this but its simple for now
        #bianca.set_location(5,5)
        #seraphim.set_location(6,6)
        #paladin.set_location(7,7)
       
        BM.xadj.value = 872 # set battle view, this does not really need to be changed erver
        BM.yadj.value = 370
       
        create_ship(MissileFrigate(), (13,5)) # This creates a ship with default armament
        create_ship(MissileFrigate(), (15,5), [PactFrigateMissile()]) # creates a modified ship
        enemy_ships[-1].max_en=80
        enemy_ships[-1].boss = False
        enemy_ships[-1].Faction ='PACT'
        enemy_ships[-1].name = 'Damaged Missile Frigate'
        enemy_ships[-1].max_hp = 100
        enemy_ships[-1].hp = 30
       
        create_cover((8,4)) #creates cover
        create_cover((1,1))
   
    $ PlayerTurnMusic = "music/Titan.ogg" #set music
    $ EnemyTurnMusic = "music/Dusty_Universe.ogg"
    return # goes back to setupmissionnews where it was called

label missionnews: # This is the actual mission, very simple
   
    $ BM.battle() # Calls your turn and waits for a response
    if BM.battlemode == True: #When set to false battle ends
        jump missionnews # Loop Back
    else:
        jump after_missionnews

label after_missionnews:
    "Newsperson" "So at least some of the Cera military are known to have survived the PACT assault"
    kay "So... At lease someone in Cera may know we survived"
    ava "But PACT are more likely to come after us now we're in the public view"
    jump dispatch # shipmap
   
   
label aftercredits_mod:
    "The prototypes' words rang hollow, for Captain Shields and the crew of the Sunrider lived happily ever after. The End."
   
    #From here is copied directly from overridden aftercredits7 label
    play sound "sound/drum.ogg"
   
    $ renpy.pause(1.0)

    stop music fadeout 1.5
    scene white with dissolvelong

    play sound "sound/drumroll.ogg"

    "And now... The results of our great waifu war!"
    "And the winner is..."

    show poll6:
        xalign 0.5 yalign 0.5
    with dissolve
   
    pause
   
    "... ... ..."
   
    show ava hs armscrossed pout with dissolve
   
    ava "... ... ..."
   
    show ava hs handhair blush with dissolve
   
    ava "Idiot."
   
    $ renpy.full_restart()
    return

You are very welcome!

I'm glad it's been useful but it wasen't ever ment to be played... How could you put yourself through the awful story?  ;D

Check if the image of ava is defined, not all of the images in the character folders were actualy registered in the character rpys and if there is an unknown file image, renpy displays a gray silhouette

I can see why people would have been disgruntled by LD, and I'm sure they would apreciate an alternate sence of cloasure, (from a purely selfish standpoint) have you considered takeing it further? It would be fairly easy to create a spin-off adventure after all.

Either way I look forward to seeing it.
« Last Edit: June 19, 2016, 10:42:55 pm by The Bigfoot »
I make spaceships! - http://innomenpro.com/forums/index.php?topic=1366.0
Massive Modding tutorial here: http://innomenpro.com/forums/index.php?topic=1251.0
New Award: John Titor [Sep 17, 2015, 10:16:07 PM]:   BigFoot is the official Evil Genius fro mteh forum

Offline Carthienes

Re: [Tutorial][7.2] Story mod makeing with explained examples
« Reply #14 on: June 20, 2016, 08:41:41 am »
Than you for the suggestion - I checked ava.rpy for the character:

Code: [Select]
    image ava uniform altneutral angry:
        "Character/Ava/ava_uniform_altneutral_angry.png"
        yanchor 0.51 ypos 1.0
        xanchor 0.5
        zoom 0.6255
        subpixel True

The line I had to change was the same show ava uniform altneutral angry that I still used to make her appear in the first place, so it seem best to just use the character name if the sprite is not being changed. Not sure why.

As for the ending mod - I am first working on an expansion to the existing mod, one that will tell the player a few things based on their morality and relationship scores. I do no see any way to call a case/switch statement, or construct a String dynamically, so it is likely to end up a horrible mess of nested if statements.

I do want to write up a full chapter to end the series on, however that is going to take a while. I have a few ideas as to what to put in it (looking at the Kickstarter page gave me a few more) but pulling it together into a reasonably consistent plot is going to take some thought.

More worryingly, the idea I liked best has space whales in it - How am I going to make space whales with these images?