JVDesignStudio · Python Modding Workshop

Mod
OpenRCT2
With Python

Add your own shops, create custom menu items, and upgrade stalls step by step, watching your park update live on the right.

Python 3 OpenRCT2 Modding 12 Steps Ages 11+ 🆓 Free
🎢 Start Modding
What you'll add
Custom shop nameYOU decide
Menu items & pricesYOU decide
Stall colour & typeYOU decide
Customer happinessYOU decide
Park earningsYOU decide
0 XP
Level 1
🔥0
Your Progress 0 / 12 steps
1
🎢
What Is OpenRCT2 Modding?
Learn what OpenRCT2 is and set up your mod file
Active
🎯
Goal for this step

Understand what OpenRCT2 modding is and see the starter file structure.

OpenRCT2 is a free, open-source remake of RollerCoaster Tycoon 2. Because it's open source, you can mod it — changing and adding things like shops, rides, and more. We'll use Python to write mod scripts that define custom stalls, menu items, and prices. Think of it as telling the game "add this shop, sell these things."

In the real OpenRCT2 modding system, mods are written in JavaScript or JSON files but the logic you learn here is identical. We write it in Python for the course, which is cleaner to read and the concepts transfer directly.

The main file we'll be editing is called custom_shops.py. Here's what an empty one looks like:

The starter file Python
custom_shops.py
# OpenRCT2 Custom Shops Mod # Written for JVDesignStudio Python Workshop shops = [] # our list of custom stalls items = [] # our list of menu items def register_mod(): """Called by OpenRCT2 when the park loads.""" pass # we'll fill this in!
📁rct2_mods / custom_shops.pyYOUR MOD
📁rct2_mods / items.jsonITEM DATA
Python uses lists (square brackets []) to store groups of things like all your shops or all your menu items. We'll add things to those lists step by step.
✏️
Fill in the Blanks
+15 XP
In Python, a uses square brackets [] to store groups of things. The function is called when the park loads.
🧠
Knowledge Check
+15 XP
What does open source mean for OpenRCT2?
AYou have to pay to download it
BAnyone can see the code and modify or add to the game
CThe game is always online and connected to the internet
DOnly the original developers can change things
2
🏷️
Name Your Park
Create your first Python variable — a park name string
Locked
🎯
Goal for this step

Learn what a variable and a string are by naming your park.

Every mod should have a park name. This is the string (piece of text) that appears above the entrance gate. In Python, text is stored in a variable using quote marks.

custom_shops.py — Park Setup Python
custom_shops.py
# Step 2: Give your park a name PARK_NAME = "Pixel Paradise" # This shows above the entrance gate # It's a STRING — text wrapped in quote marks PARK_COLOUR = "Forest Green"
A variable is like a labelled box that stores a value. PARK_NAME = "Pixel Paradise" creates a box called PARK_NAME and puts the text "Pixel Paradise" inside it. Python uses ALL_CAPS for values that shouldn't change (called constants).
💻
Code Challenge
+20 XP
Fill in the blanks to create a variable called PARK_NAME with a string value:
= ""
💡 Hint: The variable name uses ALL_CAPS and stores a park name string.
🧠
Knowledge Check
+15 XP
What is a string in Python?
AA number stored in a variable
BA list of items in square brackets
CText wrapped in quote marks
DA function that runs code
3
🏪
Add Your First Shop
Use a Python dictionary to define a stall with key-value pairs
Locked
🎯
Goal for this step

Create a dictionary to define a shop with name, type, and colour.

Time to add a stall! In Python, a group of related values is stored in a dictionary — like a mini database for one thing. Each shop is a dictionary with keys like "name", "type", and "colour".

custom_shops.py — First Stall Python
custom_shops.py
# Step 3: Define your first shop (a dictionary) my_first_shop = { "name": "Pip's Ice Creams", "type": "food_stall", "colour": "orange", } # Add it to our shops list using .append() shops.append(my_first_shop)
A dictionary uses curly braces {} and stores key: value pairs. Like a form — "name" is the label, "Pip's Ice Creams" is the answer. .append() adds the shop onto the end of our list.
Each stall type changes how much money visitors spend. souvenir_shop earns the most per visit but fewer people buy!
💻
Code Challenge
+20 XP
Fill in the blanks to add the shop to the list:
my_first_shop = "name": "Pip's Ice Creams", "type": "food_stall", } shops.(my_first_shop)
💡 Hint: Dictionaries use curly braces. The method to add to a list starts with "a".
🧠
Knowledge Check
+15 XP
What symbol wraps a Python dictionary?
ACurly braces { }
BSquare brackets [ ]
CParentheses ( )
DAngle brackets < >
4
🍽️
Build a Menu
Use a list of dictionaries to store menu items and prices
Locked
🎯
Goal for this step

Create a list of menu items, each with a name and price.

A stall needs things to sell! In Python, a list (square brackets) lets us store multiple items. Each item on the menu is a small dictionary with a name and a price.

custom_shops.py — Menu Items Python
custom_shops.py
# Step 4: Build a menu (a list of dictionaries) items = [ {"name": "Strawberry Cone", "price": 2.50}, {"name": "Choc Dip", "price": 3.00}, {"name": "Rainbow Waffle", "price": 4.00}, ]
A Python list of dictionaries is the standard way to represent things that share the same fields. Every item has a "name" and a "price" — so we store them all in a list and can loop through them later.
Visitors in OpenRCT2 will complain if prices are too high. If happiness drops, lower your prices!
True or False
+15 XP
A list uses square brackets [ ] to store multiple items.
Each item in the menu list is stored as a string.
Every menu item dictionary has both a "name" and a "price" key.
🧠
Knowledge Check
+15 XP
How would you access the price of the first item in the items list?
Aitems.price[0]
Bitems["price"]
Citems[0]["price"]
Dprice(items, 0)
5
🔁
A Second Stall + Loops
Add a second shop and learn how for loops register all stalls
Locked
🎯
Goal for this step

Add a second stall and use a for loop to register all shops.

Parks need variety! Let's add a second stall. We'll also learn how Python's for loop goes through every item in a list — exactly how the game registers all your stalls when the park loads.

custom_shops.py — Second Stall Python
custom_shops.py
# Step 5: A second stall my_second_shop = { "name": "Lumo's Juice Bar", "type": "drink_stall", "colour": "blue", } shops.append(my_second_shop) # How the game registers every shop: for stall in shops: register_stall(stall) print(f"Registered: {stall['name']}")
A for loop says "do this for each item in the list". for stall in shops: goes through each stall one at a time and runs the indented code below it. The game uses this same pattern to load all mods at startup.
Two stalls bring in more visitors! The preview will show a second stall appearing in the park.
💻
Code Challenge
+20 XP
Fill in the blanks to write a for loop that registers each stall:
for in : register_stall(stall)
💡 Hint: The loop variable is one item from the list. The list is called "shops".
🧠
Knowledge Check
+15 XP
What does for stall in shops: do?
ACreates a new shop called "stall"
BGoes through each shop in the list one at a time and runs the indented code
CDeletes every stall from the shops list
DAdds "stall" as a new key in each dictionary
6
⚙️
Writing a Function
Build a reusable create_stall() function with parameters
Locked
🎯
Goal for this step

Write a reusable function that creates and returns stall dictionaries.

A function is a reusable chunk of code that does a specific job. Instead of writing the same "add a stall" code over and over, we write a function called create_stall() and call it whenever we need it.

custom_shops.py — create_stall() function Python
custom_shops.py
# Step 6: A reusable function def create_stall(name, stall_type, colour="orange"): """Build and return a stall dictionary.""" stall = { "name": name, "type": stall_type, "colour": colour, } shops.append(stall) return stall # Now we can add stalls in one clean line each: create_stall("Echo's Doughnuts", "food_stall") create_stall("Merch Hut", "souvenir_shop", "purple")
A function uses def, then a name, then parameters in brackets (the things it needs to do its job). The word return sends a result back. The colour="orange" part is a default value — if you don't say a colour, it uses orange automatically.
✏️
Fill in the Blanks
+15 XP
You define a function with the keyword . The function sends back a result using the keyword . A parameter with a value like colour="orange" is called a value.
🧠
Knowledge Check
+15 XP
If you call create_stall("Tacos", "food_stall") without giving a colour, what colour will it use?
AIt will crash with an error
BIt will be blank / no colour
CIt will use "red" as a fallback
DIt will use "orange" — the default value
7
🔀
If Statements & Pricing Rules
Use if/elif/else to make visitors decide whether to buy
Locked
🎯
Goal for this step

Write if/elif/else logic to check whether visitors will buy based on price.

OpenRCT2 visitors check if prices are fair before they buy. We can code that logic too — with if/elif/else — Python's way of making decisions.

custom_shops.py — Price Checker Python
custom_shops.py
# Step 7: Should visitors buy based on price? def will_visitor_buy(price, happiness): if price <= 2: return "Definitely buying — great value!" elif price <= 4: return "Probably buying if happy enough" else: return "Too expensive — visitor walks away!" # Test it with a price: result = will_visitor_buy(3, 80) print(result)
if checks a condition. If it's true, the indented code runs. elif (else if) checks another condition if the first was false. else runs if none of the conditions matched. Python uses indentation (spaces) to show which code belongs inside the if.
💻
Code Challenge
+20 XP
Fill in the blanks to complete the pricing logic:
if price <= 2: return "Great value!" price <= 4: return "Probably buying" : return "Too expensive!"
💡 Hint: "elif" is short for "else if". "else" catches everything remaining.
🧠
Knowledge Check
+15 XP
If the price is 5, which branch of the if/elif/else runs?
AThe "if" branch (price <= 2)
BThe "else" branch — too expensive
CThe "elif" branch (price <= 4)
DAll three branches run
8
🛍️
Souvenir Shop + Item Lists
Build nested data — a list inside a dictionary
Locked
🎯
Goal for this step

Create a souvenir shop with a list of items nested inside the dictionary.

Now let's build a souvenir shop with a full list of items inside it — a list inside a dictionary. This is called nested data and it's everywhere in real game modding.

custom_shops.py — Souvenir Shop Python
custom_shops.py
# Step 8: Souvenir shop with nested items souvenir_shop = { "name": "Pip's Gift Shop", "type": "souvenir_shop", "colour": "purple", "items": [ # a list INSIDE the dictionary! {"name": "Park T-Shirt", "price": 12}, {"name": "Pip Plushie", "price": 18}, {"name": "Snow Globe", "price": 9}, ] } shops.append(souvenir_shop)
Nested data is a list or dictionary inside another. Here, "items" is a key whose value is a whole list of dictionaries. Real game files use this pattern constantly — rides have lists of cars, parks have lists of rides, and so on.
Souvenir shops earn more per sale but visitors don't buy as often. Best placed near ride exits!
🧠
Knowledge Check
+15 XP
What is "nested data"?
AData that has been deleted from the list
BData stored only in variables, never in lists
CA list or dictionary inside another list or dictionary
DTwo functions that call each other
9
⬆️
Upgrade Your Stalls
Modify dictionary values after creation to add upgrades
Locked
🎯
Goal for this step

Learn how to modify and add new keys to a dictionary after it's been created.

In OpenRCT2 you can upgrade stalls over time — fancier decor, extra items, better machines. In Python this means updating a dictionary's values after it's been created.

custom_shops.py — Upgrade System Python
custom_shops.py
# Step 9: Upgrade a stall's stats def upgrade_stall(stall, level): """Apply upgrades based on level (1, 2 or 3).""" if level >= 1: stall["sign"] = "fancy_sign" stall["extra_item"] = "seasonal_special" if level >= 2: stall["machines"] = "soft_serve_machine" stall["queue_length"] = 20 if level >= 3: stall["music"] = "jingle" stall["staff"] = 2 # Upgrade the first stall to level 3: upgrade_stall(shops[0], level=3)
Dictionaries are mutable — you can change their values after creation. stall["sign"] = "fancy_sign" adds a new key or overwrites an existing one. This is how upgrade systems work in almost every game.
A Level 3 stall will attract more visitors and earn more. Watch happiness go up!
True or False
+15 XP
You can add new keys to a Python dictionary after it's been created.
stall["music"] = "jingle" either adds or overwrites the "music" key.
Once a dictionary is created, its values can never be changed.
🧠
Knowledge Check
+15 XP
What does stall["staff"] = 2 do?
AAdds (or updates) a key called "staff" with the value 2
BDeletes the "staff" key from the dictionary
CCreates a new dictionary called "staff"
DPrints the number 2 to the console
10
🎉
Complete Your Mod!
Put all the pieces together into a final working mod file
Locked
🎯
Goal for this step

Write the final register_mod() function that brings everything together.

You've learned variables, dictionaries, lists, loops, functions, if/else, and nested data — that's real Python. Now let's write the final register_mod() function that would actually run in OpenRCT2.

custom_shops.py — Final Mod File Complete!
custom_shops.py
# ═══════════════════════════════════════ # OpenRCT2 Custom Shops Mod — COMPLETE # Made at JVDesignStudio # ═══════════════════════════════════════ PARK_NAME = "My Pixel Park" shops = [] items = [] def create_stall(name, stall_type, colour="orange"): stall = {"name":name,"type":stall_type,"colour":colour} shops.append(stall) return stall def register_mod(): print(f"Loading mod: {PARK_NAME}") for stall in shops: register_stall(stall) print(f" ✅ {stall['name']} opened!") print(f"Mod loaded — {len(shops)} stalls added.") create_stall("Pip's Ice Creams", "food_stall") create_stall("Lumo's Juice Bar", "drink_stall", "blue") create_stall("Echo's Gift Shop", "souvenir_shop", "purple") register_mod()
You built a complete Python mod! The concepts you used — variables, lists, dicts, loops, functions, if/else — are exactly what real game modders and developers use every day.
🧠
Knowledge Check
+15 XP
What does len(shops) return?
AThe name of the first shop
BThe total price of all shops combined
CThe number of items in the shops list
DThe last shop in the list
11
💾
Save Your Mod to a File
Use Python's json module to save shop data to a JSON file
Locked
🎯
Goal for this step

Learn how to save your mod's data to a JSON file using Python's built-in json module.

Real mods save their settings so they're remembered next time. Python's built-in json module lets you write your shop list to a file and read it back — in just a few lines.

custom_shops.py — Saving to JSON Python
custom_shops.py
import json # built-in Python module def save_mod(filename="my_park_mod.json"): # Build a data object to save: data = { "park_name": PARK_NAME, "shops": shops, } # Open the file and write JSON to it: with open(filename, "w") as f: json.dump(data, f, indent=2) print(f"Mod saved to {filename}") register_mod() save_mod()
import json loads Python's JSON module. with open(...) as f: opens a file safely — Python closes it automatically when the block ends. json.dump() converts your Python dictionary into a JSON text file. indent=2 makes it readable with nice spacing.
The output file looks exactly like what OpenRCT2's real mod system uses — JSON is the universal language of game data files.
💻
Code Challenge
+20 XP
Fill in the blanks to save data to a JSON file:
import with open(filename, "w") as f: json.(data, f, indent=2)
💡 Hint: The module is called "json" and the method to write is "dump".
🧠
Knowledge Check
+15 XP
What does with open(filename, "w") as f: do?
ADeletes the file from the computer
BOpens a file for writing and automatically closes it when done
CDownloads a file from the internet
DCreates a new Python function called "f"
12
📂
Load Your Mod Back
Read saved JSON data back into Python to restore your mod
Locked
🎯
Goal for this step

Load mod data from a JSON file so the game remembers your stalls between sessions.

Saving is only useful if you can load it back. We use json.load() to read the file and rebuild the shops list — so the mod remembers everything even after the game restarts.

custom_shops.py — Loading from JSON Python
custom_shops.py
import json import os # lets us check if a file exists def load_mod(filename="my_park_mod.json"): # Check the file actually exists first: if not os.path.exists(filename): print("No save file found — using defaults") return with open(filename, "r") as f: data = json.load(f) # Restore the data: global PARK_NAME, shops PARK_NAME = data["park_name"] shops = data["shops"] print(f"Loaded {len(shops)} stalls from {filename}") # Load on startup, then register: load_mod() register_mod()
global tells Python we want to change the variable at the top of the file, not create a new local one inside the function. os.path.exists() checks if the file is there before trying to open it — without this the mod would crash on first run when there's no save yet.
You now have a complete save/load system. Your mod remembers your park name and all your stalls between sessions — exactly like a real game mod!
✏️
Fill in the Blanks
+15 XP
To read a JSON file, use json.. To check if a file exists before opening it, use os.path.. The keyword lets a function change a variable defined at the top of the file.
🧠
Knowledge Check
+15 XP
Why do we check os.path.exists(filename) before opening?
ATo make the file read faster
BTo delete old save files automatically
CTo convert the file from JSON to Python
DTo prevent a crash if the file doesn't exist yet (like on first run)
🎉🎢🐍🎊🏪
You Built an OpenRCT2 Mod!

Amazing work! You've learned variables, strings, dictionaries, lists, loops, functions, if/else, nested data, and file I/O — real Python concepts used by professional game modders every day.

0
Total XP
1
Level
0
Best Streak
0%
Accuracy
🔧 Back to All Workshops →
⭐ View My Progress & Certificates

This workshop was free and took many hours to build. If it helped you learn something new, consider supporting the project.

☕ Support on Ko-fi
Keep Building
🐍Python · Live BuilderPython Game BuilderStart → 🏰C++ · Live BuilderTower Defence BuilderStart → ⚙️Godot 4 · 8 ModulesGodot PlatformerStart → 🔧All ResourcesThe WorkshopVisit →

🛠️ Free In-House Dev Tools

Make It Yours

Use these free browser tools alongside this workshop to create custom sprites, sounds, levels and colour schemes for your game. No installs. Free forever.

🎨
Pixel Studio
Draw sprites & animations
🎨
Colour Palette
Build a game-ready colour scheme
🎵
SFX Studio
Create custom sound effects
🎲
Game Idea Gen
Random game concepts & prompts
🛠️ See All 20 Free Tools →