Touhou: Fading Illusion

Touhou: Fading Illusion

Otillräckligt med betyg
Mods Creation Guide
Av GiMark
In this guide, you can find all the information needed to create a simple mod.
   
Utmärkelse
Favorit
Favoritmarkerad
Avfavoritmarkerad
Foreword
Greetings, modding enthusiasts! In this guide, you will find the basic information needed to create a mod. Before you start making mods, I recommend that you familiarize yourself with the rules for creating fan content.

WARNING: Currently, the mod upload tool is only available for Windows users.

NOTE: Images in the "_game_resources" folder and some parts of this guide may contain spoilers.

Content Restrictions
Requirements of the Original Game Series Creator
Since Touhou: Fading Illusion is a fan-made work based on the Touhou Project universe, your mods must also comply with the requirements set by the original game series creator, ZUN. Specifically, your mods must NEVER include:
  • Anything that means to harm Touhou Project’s reputation.

  • Anything that infringes upon other intellectual property.

  • Anything that means to mistake your Fan Content as one of the official Touhou Project titles.

  • Anything that is extracted from official Touhou Project games.

  • Ending scenes from official Touhou Project games.

  • Anything that means to advertise personal beliefs beyond the bounds of fiction.

  • Other Touhou Fan Content without the creator’s permission.

  • Excessive sexual content that is considered unlawful.

  • Anything that promotes hatred against individuals or groups.

  • Any photographs of ZUN himself without ZUN's permission.
You can review the full list of requirements on this website[touhou-project.news].

In addition, your mods must not contain any malicious code. If you do not wish to distribute the source code, you should contact us, and we will allow you to publish your mod after privately reviewing the source code. By starting to create a mod, you confirm that you have read this warning and agree to it. In case of violations, we reserve the right to hide or block your mod.

If you have any questions, we suggest reaching out on our team's Discord server[discord.gg].
Getting Started
Well, if you agree with everything mentioned above, let's get to work. First, you will need a convenient text editor, as you’ll be working with text and code A LOT. The ".rpy" files you’ll be dealing with can be opened with the standard built-in Notepad, but we recommend something more advanced, like VS Code or Notepad++. Both of these programs are free and publicly available.

After that, make sure that Touhou: Fading Illusion is installed on your computer, and subscribe to the official mod, which will help you understand how the code works and provide tools for uploading.
Creating a Mod Folder
Navigate to the Steam or SteamLibrary folder on the same drive where Touhou: Fading Illusion is installed, and then locate the folder at the path "steamapps\workshop\content\2132480\3565758917". This folder should appear as soon as you download our mod.
Don’t be intimidated by the numbers in the folder names – these are Steam’s identifiers, and you’ll get one for your mod as well when you create it.

Inside our mod’s folder, open the "_upload_app" folder and launch the application. In the application, enter your mod’s name in the left menu and click the "Create Item" button in the bottom left corner. After that, a folder for your mod’s content will appear in the WorkshopContent folder, along with a file named *your mod name*.workshop.json – this is the file we need (we recommend saving it somewhere after you fill it out).
Open this file, copy the value of the "publishedfileid" field, and create a folder with the same name in the "steamapps\workshop\content\2132480" directory. Since users will download your mod to this same path, it makes sense to develop it directly in this location. Create the files mod_info.rpy and script.rpy in your mod’s folder, then open the corresponding mod_info.rpy file in our mod’s folder using a text editor.
Mod Information
Inside mod_info.rpy, you can see the following code:
init 99 python:
    tfi_official_mod_path = os.path.join(os.path.dirname(os.path.dirname(renpy.config.basedir)), "workshop/content/2132480/3565758917").replace("\\","/")
    global_game_mods["tfi_official_mod"] = {
      "start_scene": "tfi_official_mod_scene1",
      "description": _("This is a tutorial mod that also serves as a template for creating other mods. Open the folder steam/steamapps/workshop/content/2132480/3565758917 to look at the code and images in more detail."),
      "name": _("Tutorial mod"),
      "image": os.path.join(tfi_official_mod_path, "images/preview.png").replace("\\","/"),
    }
For your mod, you will need to copy this code into your own mod_info.rpy file and make a few changes:
  • Replace tfi_official_mod in global_game_mods["tfi_official_mod"] with any other unique name in Latin characters. This is your mod’s identifier within the game files, and to avoid conflicts with other mods, we recommend using your mod’s name in Latin characters and later using it for naming variables and scenes.

  • Change the value of the start_scene field – this is the name of the first scene the player will transition to after launching your mod. For simplicity, copy the name of your mod mentioned above and add "_scene1" to it, as scene names must also be unique.

  • The description and name fields are the text that will be visible to users in the in-game mod selection menu. You can fill them out as you wish, but keep in mind that space is limited. You can use the {size} tag to increase or decrease the text size.
    "{size=+2}This text is bigger.{/size}"
    "{size=-2}This text is smaller.{/size}"

  • The image field is the preview image for your mod, which will also be displayed in the mod selection menu. You can leave this field empty or remove it entirely if you don’t have an image – in that case, a placeholder with a question mark will be shown.

In the end, you should have something like this:

Once you’re done with mod_info.rpy, copy the information from our script.rpy file into your own. Let’s break down all the code provided.
"init:" Block
This section contains variables you’ll want to define for your mod. For example, the command "define tfi_official_mod_dev = Character(_('TFI Main Dev'))" creates a new character named "TFI Main Dev". You can create as many characters as you like, but ensure that the variable names for your characters are unique and do not conflict with our variables or those from other mods. We recommend using a unique prefix for your mod in the names here as well.

If a character is situational, you can skip defining a variable and use the character’s name directly before their dialogue.
"TFI Main Dev" "Hello!"
"TFI Player" "Hey!"
The table below lists the variables currently used in our game, which you can reference as needed. As new characters are introduced to the story, we will add new variables.

Official Game Series Characters

Name
Tag
Name
Tag
Name
Tag
Name
Tag
Name
Tag
Reimu
rei
Marisa
mar
Byakuren
byak
Rin
orin
Rinnosuke
korin
Yukari
yuk
Mokou
moko
Kanako
kan
Youmu
youmu
Sanae
san
Kasen
kas
Clownpiece
clown
Eirin
eirin
Reisen
reisen
Nazrin
naz
Satori
sat
Utsuho
okuu
Suwako
suwa
Komachi
koma
Eiki
eiki
Chen
chen
Ran
ran
Aya
aya
Yuugi
yuugi
Mima
mima
Mamizou
mami
Sumireko
sum
Meiling
mei
Kokoro
kokoro
Sakuya
sak
Koishi
koi
Remilia
remi
Mizuchi
mizu
Seija
seija
Flandre
flan
Hecatia
heca

Secondary or Original Characters

Name
Tag
Name
Tag
Name
Tag
Name
Tag
Name
Tag
Girl
girl
Hitomu
hito
Bake-danuki
bake
Rabbit
usausa
Tengu
tengu
Kappa
kappa
Yamaraja
yamaraja
Itime-kozo
itime

The "tfi_official_mod_path" variable serves as a shortcut to the path of your mod’s folder from the main game folder. If you use any custom images, this variable will be useful. All you need to do is replace "tfi_official_mod" in the variable name with your mod’s name, as before, and update the name of the last folder in the path "workshop/content/2132480/3565758917". You can reference all files from the "3565758917/_game_resourses" folder without using this variable, as they are part of the main game. However, for your unique files, you’ll need to include it.
define mod_name_path = os.path.join(os.path.dirname(os.path.dirname(renpy.config.basedir)), "workshop/content/2132480/your_folder").replace("\\","/")
show image("[mod_name_path]/images/your_image1.png")
show image(Transform("images/backgrounds/hakurei_shrine_inter_d1.jpg", zoom=0.5))
Scenes and Lines
Next comes the start of a scene, defined by a label to which you assign a unique name. Previously, you specified the starting scene as "*your mod*_scene1", so now replace the name of this label with it.

If everything is ready, the only thing left is to come up with a plot and write it down. The text should be displayed on the screen in portions to fit within the dialogue window. If a character’s identifier is placed before a line, their name will be shown alongside the text. If there’s no identifier, only the text will be displayed.

The game also supports simultaneous lines from two or three characters. All you need to do is add (multiple = 2) or (multiple = 3) to the right of the line:
rei "Good luck." (multiple = 2)
mar "See ya." (multiple = 2)
Images
Let’s say you’ve written your dialogue, but what about images? They’re a bit more complex. We recommend checking out the relevant section of the RenPy documentation[www.renpy.org] if you don’t find answers to your questions in the examples below.

Backgrounds and Static Images
You can display an image on the screen using the show image("path to image") command. Since the game’s target resolution is 1920x1080, and many images are created in higher resolutions, they need to be scaled down using the Transform() command before being displayed. For backgrounds and some CGs, we used a scaling factor of 0.5, and for sprites, 0.6, but you can use any value you prefer. It’s also recommended to use the scene command at the start of a scene, which replaces all other images.
scene image(Transform("images/backgrounds/human_village1_m.jpg", zoom=0.5))
show image(Transform(tfi_official_mod_reimu_happy, zoom=0.6))

Dynamic Sprites
In addition to this, in Touhou: Fading Illusion, we used some non-standard RenPy methods to optimize the game or add new possibilities for direction. Specifically, we added a new layer in the image display system where layered character sprites are shown.

In our mod, you can see the following code:
$ chars = 2
$ elems = 13
$ scale = 0.6

$ pict_ = [
["none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png",],
["none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png","none.png",],
]

$ pict_1 = [
[
"none.png",
"none.png",
"none.png",
"sprites/Sakuya/sak_body1.png",
"sprites/Sakuya/sak_body1_brow1.png",
"sprites/Sakuya/sak_body1_eyes1_d.png",
"sprites/Sakuya/sak_body1_mouth2.png",
"sprites/Sakuya/sak_body1_leftarm2.png",
"sprites/Sakuya/sak_body1_rightarm6.png",
"none.png",
"none.png",
"none.png",
"none.png",
],
[
"none.png",
"none.png",
"none.png",
"sprites/Nazrin/naz_body1.png",
"sprites/Nazrin/naz_body1_brow1.png",
"sprites/Nazrin/naz_body1_eyes5_f.png",
"sprites/Nazrin/naz_body1_mouth18.png",
"sprites/Nazrin/naz_body1_arms1.png",
"none.png",
"none.png",
"none.png",
"none.png",
"none.png",
],
]

$ xp = [111000, 111000, 111000]
$ yp = [-130, -130, -130]
$ copy()
show screen _Pers_ onlayer add_l
Here, you can see how Sakuya and Nazrin’s sprites are assembled in the code: we use layered rendering to easily change facial expressions or hand positions without limiting variations. Before specifying specific sprite elements, you need to define the variables chars (number of characters), elems (number of elements), and scale (sprite scaling factor), and also assign arrays similar to the sprites to the pict_ variable, but with "none.png" images.

Sprite positions are set by two variables – xp and yp, which control horizontal and vertical positioning, respectively. These variables can be set individually for each sprite, e.g., xp[1] = …, or for all sprites at once using an array, e.g., xp = […, …, …].

Additionally, this method uses a separate layer for displaying sprites: "show screen _Pers_ onlayer add_l". This layer is in front of the standard layer for images, so sprites will always appear in front of all other images. If you want to display an image in front of the sprites, you’ll need to add "onlayer add_l".

However, all of this is optional, and you can pre-assemble a single sprite from layers, assign it to the pict_1 variable in a similar way, and then switch to any other sprite, or use standard RenPy methods.

The code below shows examples of changing sprite elements or displaying a "complete" Reimu sprite from the mod’s files.
$ pict_[0][4] = "sprites/Sakuya/sak_body1_brow3.png" # 4
$ pict_[0][5] = "sprites/Sakuya/sak_body1_eyes3_f.png" # 5
$ pict_[0][6] = "sprites/Sakuya/sak_body1_mouth6.png" # 6
$ pict_[0][7] = "sprites/Sakuya/sak_body1_leftarm3.png" # 7
$ pict_[0][8] = "sprites/Sakuya/sak_body1_rightarm1.png" # 8

$ pict_[1][4] = "sprites/Nazrin/naz_body1_brow3.png" # 4
$ pict_[1][5] = "sprites/Nazrin/naz_body1_eyes3_f.png" # 5
$ pict_[1][6] = "sprites/Nazrin/naz_body1_mouth10.png" # 6
$ pict_[1][7] = "sprites/Nazrin/naz_body1_leftarm1.png" # 7
$ pict_[1][8] = "sprites/Nazrin/naz_body1_rightarm4.png" # 8

hide image(Transform(tfi_official_mod_reimu_happy, zoom=0.6))

show image(Transform(tfi_official_mod_reimu_sad, zoom=0.6)):
    xalign 0.8
    yalign -0.1
with dissolve

Color Changes
You also have the option to change an image’s color scheme using the blur() function with three parameters – hue, brightness, and saturation. For example, blur(image, 320, 120, 100) will change the hue to 320, brightness to 120, and saturation to 100 for the image.
$ pict_[0][3] = blur("sprites/Sakuya/sak_body1.png", -10, -0.22, 0.9) # 3
$ pict_[0][4] = blur("sprites/Sakuya/sak_body1_brow3.png", -10, -0.22, 0.9) # 4
$ pict_[0][5] = blur("sprites/Sakuya/sak_body1_eyes3_f.png", -10, -0.22, 0.9) # 5
$ pict_[0][6] = blur("sprites/Sakuya/sak_body1_mouth6.png", -10, -0.22, 0.9) # 6
$ pict_[0][7] = blur("sprites/Sakuya/sak_body1_leftarm3.png", -10, -0.22, 0.9) # 7
$ pict_[0][8] = blur("sprites/Sakuya/sak_body1_rightarm1.png", -10, -0.22, 0.9) # 8
Effects
RenPy supports a variety of visual effects for images appearing or disappearing. You may have noticed that many commands in the provided examples end with "with dissolve". This is used to make images appear or disappear with a 1-second transition rather than instantly. There’s also another built-in version, dissolve_f, which lasts 0.25 seconds and is used when changing sprite elements to avoid overly prolonging game time. You can create your own transition version using a similar variable:
define dissolve_five = Dissolve(5) # a 5-second transition
For the most part, we use only these, but you can use other effects that are applied in a similar way. You can read more about the effects in the RenPy documentation[www.renpy.org].
Music and Sounds
You can play or stop music using the renpy.music.play and renpy.music.stop commands. After specifying the file path, you can indicate the channel in which the track will be played, as well as the effect for starting or fading out the track.
$ renpy.music.play("sounds/ambience/crowd_medium1.ogg", channel="music_2", synchro_start=True, fadein=2.0)
$ renpy.music.stop(channel="music_2", fadeout=1.5)
There are three channels for music – music, music_1, and music_2, as well as one channel for one-time sound playback – sound.
Choices
We have modified the standard choice menu to display not only the possible options but also the question posed to the player. To display the question, you need to update the menu_question variable:
$ menu_question = _("Do you need an extra round?")

$ quick_menu = False
window hide dissolve

menu:
    with dissolve
    "Yes, please!":
      $ quick_menu = True
      window show dissolve
      jump tfi_official_mod_scene1
    "Nah, I got it.":
      $ quick_menu = True
      window show dissolve
Using Variables
You can use variables, for example, to track the player's choices. Variables can be text (= "one"), numeric (= 1), or logical (= True), and you can manipulate them using different conditions, such as if or for. You can read more about variables in the relevant section of the RenPy documentation[www.renpy.org].
$ menu_question = _("One or two?")

menu:
    with dissolve
    "One!":
      $ mod_name_player_choice = 1
      jump mod_name_scene_player_pick_one
    "Two!":
      $ mod_name_player_choice = 2
      jump mod_name_scene_player_pick_two
if your_mod_condition == 2:
    jump destitute1_scene2_5
else:
    jump destitute1_scene1_5
Translation
NOTE: Some members of our team have expressed their willingness to translate mods they like in addition to the main game, so if you’re interested, you can reach out to them on our Discord server[discord.gg].

If you plan to create a mod in multiple languages, you will need to generate translation files. To generate them, you’ll need to download RenPy 8.3.7[www.renpy.org], specify the game path in the settings, and generate a translation into desired language.
You can translate not only dialogue lines but also variables and character names: to do this, enclose the text in underscore quotes _(...) when declaring the variable:
define tfi_official_mod_dev = Character(_('TFI Main Dev'))
However, we warn you that some variables may cause errors, so it’s recommended to generate only the translation files for dialogue lines, which require a translation identifier, while the translatable text itself serves as the identifier for variables.
# translatable line
translate english prologue_scene_0_89ef4bf6:
    "Humanity has always been afraid of the unknown."

# translatable variable
translate english strings:
    old "Продолжение следует..."
    new "To be continued..."
After generating, move the tl folder to your mod’s folder and fill in the translation fields in all the new files.
We also recommend creating a similar menu at the start of your mod so users know which languages are available and that they are available at all.
menu:
    with dissolve
    "{font=fonts/Merriweather-Regular.ttf}Я хочу играть на Русском{/font}":
      $ renpy.change_language("russian")
    "{font=fonts/Merriweather-Regular.ttf}I want to play in English{/font}":
      $ renpy.change_language("english")
    "{font=fonts/SourceHanSansCN-Regular.otf}{size=26}以简体中文开始{/size}{/font}":
      $ renpy.change_language("schinese")
    "{font=fonts/NotoSansJP-Regular.ttf}{size=26}日本語でプレイする{/size}{/font}":
      $ renpy.change_language("japanese")
As of the writing of this guide, the game supports 9 languages: Russian (russian), English (english), French (french), Spanish (spanish), Brazilian Portuguese (portuguese), Traditional Chinese (tchinese), Simplified Chinese (schinese), Korean (korean), and Japanese (japanese). If you use a language that doesn’t use the alphabets of any of these languages, you will need to include new fonts in your mod.
Additional Features
Working with the Camera
Since characters are displayed on a separate screen, you’ll need to move this screen simultaneously with the standard camera layer if you want to zoom in or out of the image.
camera:
    perspective True
    gl_depth True
    zpos -1100 xpos -375 ypos -380
    easein 3.0 zpos 0 xpos 0 ypos 0
show layer add_l:
    perspective True
    gl_depth True
    zpos -1100 xpos -375 ypos -380
    easein 3.0 zpos 0 xpos 0 ypos 0

Date and Time Screen
The game includes a built-in feature to display a screen with the location, date, and time, in case your story also involves dividing events into days and hours.
$ date_place = _("Muenzuka")
$ date_date = _("April 17, 2018")
$ date_time = _("Evening, a few hours later")
show screen date with dissolve

Loading Screen
If the "reassembly" or color changes of sprites are significant, they may cause a slight delay in the game, as RenPy needs time to process and place all images individually on the screen. To make such moments less noticeable, we created a transition screen with the text "Girls are praying…". To trigger it, you’ll need to use the show screen loading command followed by hide screen loading. For smooth transitions, we hide and then show the dialogue windows with the following commands.
$ quick_menu = False
window hide dissolve

$ quick_menu = True
window show dissolve
Testing the Mod
Before publishing your mod, you need to ensure it works properly. The developer mode can help with this. To activate it, add the following code into your init block:
define config.developer = True
define config.default_developer = True
Now, if you press the Shift + R key combination, the game will reload to the same point every time changes are made to the files. This means you can quickly edit your code and immediately see the results. However, please delete it before uploading the mode.
Uploading the Mod
Once you have completed all the steps, you need to:
1. Place the mod files in the mod folder within the upload application. Don’t forget the preview image, which must be square and at least 500x500 pixels in resolution.
2. Enter all the necessary information. We recommend moving the entire _upload_app folder somewhere outside the 2132480 folder to avoid conflicts with the files of other mods in the future.
3. Add a comment and click the Submit button. All the entered information will be saved in your .workshop.json file.

If you have any remaining questions, we suggest writing them in the comments to this guide so we can update the information. We hope everything works out for you, and good luck in creating quality literature!