############################################################### ########## Multi-Choice Picker Mod for Ren'Py - v0.1 ########## #### (C) Knox Emberlyn 2025 #### ############################################################### init -999 python in dukeconfig: multichoice_enabled = True # Enable/disable multi-choice mod multichoice_text = "🔄 Experience All Choices" show_choice_numbers = True advance_key = "n" # Key to advance to next choice (alt+n = next) emergency_exit_key = "e" # Key for emergency exit/reset (alt+e) show_progress_overlay = True # Show progress overlay during multi-choice debug = False # Enable debug messages force_universal = True # Force multi-choice on ALL menus (ignore flow detection) init -999 python: def dukemsg(*args, **kwargs): if dukeconfig.debug: print(*args, **kwargs) init -1 python: # Storage for multi-choice state store.multichoice_active = False store.multichoice_choices = [] store.multichoice_current_index = 0 store.multichoice_seen_choices = set() store.multichoice_return_label = None store.multichoice_original_menu_items = [] store.multichoice_available = False store.multichoice_choice_in_progress = False store.multichoice_menu_count = 0 # Track total menus seen store.multichoice_last_selected_choice = None # Track what was last selected dukemsg("DEBUG: Multi-choice store variables initialized") init python: # Backup original functions if not hasattr(store, '_original_menu_function'): store._original_menu_function = renpy.exports.menu dukemsg("DEBUG: Original functions backed up") else: dukemsg("DEBUG: Original functions already backed up") def enhanced_menu(items, set_expr=None, args=None, kwargs=None, item_arguments=None): """Enhanced menu function with universal multi-choice support""" store.multichoice_menu_count += 1 dukemsg(f"DEBUG: enhanced_menu called #{store.multichoice_menu_count} with {len(items)} items") dukemsg("DEBUG: Items:", [item[0] if len(item) > 0 else "EMPTY" for item in items]) if not dukeconfig.multichoice_enabled: dukemsg("DEBUG: Multi-choice disabled, using original menu") return store._original_menu_function(items, set_expr, args, kwargs, item_arguments) # If we're in multi-choice mode, handle automatic selection if store.multichoice_active: dukemsg("DEBUG: In multi-choice mode, handling automatic selection") choice_result = handle_multichoice_selection(items) if choice_result is not None: dukemsg(f"DEBUG: Returning automatic choice index: {choice_result}") store.multichoice_choice_in_progress = True store.multichoice_last_selected_choice = items[choice_result][0] if choice_result < len(items) else "Unknown" return choice_result else: dukemsg("DEBUG: Multi-choice complete, using original menu") return store._original_menu_function(items, set_expr, args, kwargs, item_arguments) # UNIVERSAL CHECK - Count ALL real choices regardless of conditions dukemsg("DEBUG: Performing UNIVERSAL multi-choice check") real_choices = [] for i, item in enumerate(items): dukemsg(f"DEBUG: Item {i}: {item}") # More lenient check - if it has a label and any kind of value if len(item) >= 2 and item[0] and item[1] is not None: real_choices.append((i, item)) dukemsg(f"DEBUG: Added as real choice: {item[0]}") dukemsg(f"DEBUG: Found {len(real_choices)} real choices (universal check)") # FORCE multi-choice availability on ANY menu with 2+ choices if len(real_choices) >= 2 or dukeconfig.force_universal: # Always make multi-choice available, ignore all flow detection current_label = renpy.game.context().current dukemsg(f"DEBUG: Current label: {current_label}") # Store the original choices and menu items for multi-choice mode store.multichoice_original_menu_items = items store.multichoice_available = True dukemsg("DEBUG: Multi-choice is UNIVERSALLY AVAILABLE for this menu") else: dukemsg("DEBUG: Less than 2 real choices, multi-choice not available") store.multichoice_available = False # Return original menu return store._original_menu_function(items, set_expr, args, kwargs, item_arguments) def handle_multichoice_selection(current_items): """Handle menu selection during multi-choice mode""" dukemsg("DEBUG: handle_multichoice_selection called") # Get real choices from current items real_choices = [] for i, item in enumerate(current_items): if len(item) >= 2 and item[0] and item[1] is not None: real_choices.append((i, item)) dukemsg(f"DEBUG: Current real choices: {real_choices}") dukemsg(f"DEBUG: Current index: {store.multichoice_current_index}") dukemsg(f"DEBUG: Seen choices: {store.multichoice_seen_choices}") dukemsg(f"DEBUG: Total choices to experience: {len(store.multichoice_choices)}") dukemsg(f"DEBUG: Choice in progress: {store.multichoice_choice_in_progress}") dukemsg(f"DEBUG: Last selected: {store.multichoice_last_selected_choice}") # If we're already in progress of a choice, don't select anything new if store.multichoice_choice_in_progress: dukemsg("DEBUG: Choice already in progress, waiting for user to advance") # Return None to use original menu (let user interact normally) return None # SAFETY CHECK: If current menu has more choices than we stored, update our list if len(real_choices) > len(store.multichoice_choices): dukemsg("DEBUG: Current menu has more choices than stored, updating choice list") store.multichoice_choices = real_choices dukemsg(f"DEBUG: Updated choice list to {len(store.multichoice_choices)} choices") # Find which choice we should select based on our progress if store.multichoice_current_index < len(store.multichoice_choices): target_choice_index, target_choice_item = store.multichoice_choices[store.multichoice_current_index] target_choice_text = target_choice_item[0] # Find this choice in the current menu items selected_index = None for i, (menu_index, menu_item) in enumerate(real_choices): if menu_item[0] == target_choice_text: selected_index = menu_index break if selected_index is not None: dukemsg(f"DEBUG: Selecting choice {store.multichoice_current_index}: {target_choice_text} (menu index: {selected_index})") # Mark this choice as seen store.multichoice_seen_choices.add(store.multichoice_current_index) # Advance to next choice for next time store.multichoice_current_index += 1 return selected_index else: dukemsg(f"DEBUG: Could not find target choice '{target_choice_text}' in current menu") dukemsg("DEBUG: Available choices:", [choice[1][0] for choice in real_choices]) # All choices experienced, end multi-choice mode dukemsg("DEBUG: All choices experienced, ending multi-choice mode") end_multichoice() return None def start_multichoice(): """Start multi-choice mode""" dukemsg("DEBUG: Starting multi-choice mode") # Get real choices from the stored menu items (universal logic) real_choices = [] for i, item in enumerate(store.multichoice_original_menu_items): if len(item) >= 2 and item[0] and item[1] is not None: real_choices.append((i, item)) dukemsg(f"DEBUG: Extracted {len(real_choices)} choices from stored menu items") dukemsg("DEBUG: Choice list:", [choice[1][0] for choice in real_choices]) store.multichoice_choices = real_choices store.multichoice_return_label = renpy.game.context().current store.multichoice_active = True store.multichoice_current_index = 0 store.multichoice_seen_choices = set() store.multichoice_available = False store.multichoice_choice_in_progress = False store.multichoice_last_selected_choice = None dukemsg(f"DEBUG: Return label set to: {store.multichoice_return_label}") dukemsg(f"DEBUG: Will experience {len(store.multichoice_choices)} choices: {[choice[1][0] for choice in store.multichoice_choices]}") # Validation check if len(store.multichoice_choices) < 2: dukemsg("ERROR: Less than 2 choices detected!") renpy.notify("Error: Not enough choices detected for multi-choice mode!") return # Show start message renpy.say(None, f"Multi-Choice Mode activated! You'll experience {len(store.multichoice_choices)} choices.", interact=False) renpy.say(None, f"Press Alt+{dukeconfig.advance_key.upper()} to advance | Alt+{dukeconfig.emergency_exit_key.upper()} for emergency exit", interact=False) def advance_to_next_choice(): """Advance to next choice in multi-choice sequence""" dukemsg("DEBUG: advance_to_next_choice called") if not store.multichoice_active: dukemsg("DEBUG: Multi-choice not active, ignoring advance") return if not store.multichoice_choice_in_progress: dukemsg("DEBUG: No choice in progress, ignoring advance") renpy.notify("No choice is currently being experienced.") return dukemsg(f"DEBUG: Advancing from choice in progress") dukemsg(f"DEBUG: Seen {len(store.multichoice_seen_choices)} out of {len(store.multichoice_choices)} choices") store.multichoice_choice_in_progress = False store.multichoice_last_selected_choice = None if dukeconfig.show_choice_numbers: choice_num = len(store.multichoice_seen_choices) total_choices = len(store.multichoice_choices) if choice_num < total_choices: renpy.notify(f"Choice {choice_num} of {total_choices} completed. Moving to next choice...") # Check if we've seen all choices if len(store.multichoice_seen_choices) >= len(store.multichoice_choices): dukemsg("DEBUG: All choices completed, ending multi-choice mode") end_multichoice() return # Return to the menu to make the next choice dukemsg(f"DEBUG: Jumping back to {store.multichoice_return_label} for next choice") renpy.jump(store.multichoice_return_label) def emergency_exit_multichoice(): """Emergency exit/reset multi-choice mode""" dukemsg("DEBUG: Emergency exit called") if store.multichoice_active: renpy.notify("Emergency exit: Multi-Choice Mode terminated!") end_multichoice() else: renpy.notify("Emergency reset: All multi-choice data cleared!") # Force reset everything store.multichoice_active = False store.multichoice_choices = [] store.multichoice_current_index = 0 store.multichoice_seen_choices = set() store.multichoice_return_label = None store.multichoice_original_menu_items = [] store.multichoice_available = False store.multichoice_choice_in_progress = False store.multichoice_last_selected_choice = None def end_multichoice(): """End multi-choice mode""" dukemsg("DEBUG: Ending multi-choice mode") if not store.multichoice_active: dukemsg("DEBUG: Multi-choice already ended, ignoring") return total_choices = len(store.multichoice_seen_choices) store.multichoice_active = False store.multichoice_current_index = 0 store.multichoice_seen_choices = set() store.multichoice_return_label = None store.multichoice_original_menu_items = [] store.multichoice_available = False store.multichoice_choices = [] store.multichoice_choice_in_progress = False store.multichoice_last_selected_choice = None renpy.say(None, f"Multi-Choice Mode complete! You experienced {total_choices} different paths.", interact=False) def toggle_multichoice(): """Toggle multi-choice mod on/off""" dukeconfig.multichoice_enabled = not dukeconfig.multichoice_enabled status = "enabled" if dukeconfig.multichoice_enabled else "disabled" dukemsg(f"DEBUG: Multi-choice toggled to {status}") renpy.notify(f"Multi-Choice Mod {status}") # Replace functions dukemsg("DEBUG: Replacing enhanced functions") renpy.exports.menu = enhanced_menu dukemsg("DEBUG: Functions replaced successfully") # Screen overlay to show multi-choice button (ALWAYS visible on choice screens) screen multichoice_overlay(): if renpy.get_screen("choice"): # Always show if enabled, let user decide if dukeconfig.multichoice_enabled and not multichoice_active: button: xalign 0.98 yalign 0.02 xsize 200 ysize 40 background "#000080" hover_background "#0000FF" action Function(trigger_multichoice_mode) text dukeconfig.multichoice_text: xalign 0.5 yalign 0.5 size 14 color "#FFFFFF" # Emergency exit button (visible during multi-choice if previous choice data is available) if multichoice_active: button: xalign 0.98 yalign 0.10 xsize 200 ysize 30 background "#800000" hover_background "#FF0000" action Function(emergency_exit_multichoice) text f"🚨 Emergency Exit (Alt+{dukeconfig.emergency_exit_key.upper()})": xalign 0.5 yalign 0.5 size 12 color "#FFFFFF" # Progress overlay during multi-choice mode screen multichoice_progress(): if multichoice_active and dukeconfig.show_progress_overlay: frame: xalign 0.02 yalign 0.02 xsize 320 ysize 160 background "#000000AA" vbox: spacing 5 xfill True text "Multi-Choice Mode Active" size 16 color "#FFFFFF" xalign 0.5 text f"Menu #{multichoice_menu_count}" size 12 color "#CCCCCC" xalign 0.5 python: current_choice_num = len(multichoice_seen_choices) total_choices = len(multichoice_choices) if multichoice_choice_in_progress and multichoice_last_selected_choice: status_text = f"Experiencing: {multichoice_last_selected_choice}" elif multichoice_choice_in_progress: status_text = "Choice in progress..." else: status_text = "Ready for next choice" text f"Progress: {current_choice_num}/{total_choices}" size 14 color "#FFFF00" text status_text size 11 color "#CCCCCC" text_align 0.5 xfill True if multichoice_choice_in_progress: text f"Alt+{dukeconfig.advance_key.upper()}: Next | Alt+{dukeconfig.emergency_exit_key.upper()}: Exit" size 10 color "#00FF00" xalign 0.5 else: text "Advancing..." size 12 color "#FF8800" xalign 0.5 init python: def trigger_multichoice_mode(): """Trigger multi-choice mode""" dukemsg("DEBUG: trigger_multichoice_mode called") # Force-enable multi-choice regardless of availability detection if not store.multichoice_active: # If no menu items stored, try to use current context if not store.multichoice_original_menu_items: # This means we're forcing on a menu that wasn't detected # Just notify user and let them try again renpy.notify("Cannot start multi-choice. Reset(Alt+e) the multi-choice mod and try again") # Notify user to reset store.multichoice_available = True return dukemsg("DEBUG: Starting multi-choice mode via trigger") start_multichoice() renpy.jump(store.multichoice_return_label) else: dukemsg("DEBUG: Multi-choice already active") def multichoice_keymap(): if dukeconfig.multichoice_enabled and not store.multichoice_active: trigger_multichoice_mode() def advance_keymap(): if store.multichoice_active and store.multichoice_choice_in_progress: advance_to_next_choice() def emergency_keymap(): emergency_exit_multichoice() init python: config.overlay_screens.append("multichoice_overlay") config.overlay_screens.append("multichoice_progress") # In-game guide screen for the Multi-Choice Picker mod screen multichoice_guide(): modal True frame: xalign 0.5 yalign 0.5 xsize 800 ysize 600 background "#001122" vbox: spacing 15 xfill True yfill True # Header frame: xfill True background "#003366" text "🔄 Multi-Choice Picker Mod - Player Guide" xalign 0.5 size 24 color "#FFFFFF" # Main content in a viewport for scrolling viewport: scrollbars "vertical" mousewheel True xfill True yfill True vbox: spacing 20 xfill True # What is this mod? frame: xfill True background "#112233" vbox: spacing 10 text "What is the Multi-Choice Picker?" size 20 color "#FFFF00" text "This mod allows you to experience ALL choices in a menu automatically, one after another, without having to manually reload saves or replay scenes." size 14 color "#CCCCCC" text "Perfect for seeing all possible dialogue branches, character reactions, and story outcomes!" size 14 color "#CCCCCC" # How to use frame: xfill True background "#112233" vbox: spacing 10 text "How to Use:" size 20 color "#FFFF00" hbox: spacing 10 text "1." size 16 color "#00FF00" text "When you see a choice menu, look for the blue button:" size 14 color "#CCCCCC" text " 🔄 Experience All Choices" size 14 color "#0080FF" hbox: spacing 10 text "2." size 16 color "#00FF00" text f"OR press Alt+M at any choice menu to activate" size 14 color "#CCCCCC" hbox: spacing 10 text "3." size 16 color "#00FF00" text "The mod will automatically select the first choice" size 14 color "#CCCCCC" hbox: spacing 10 text "4." size 16 color "#00FF00" text "Play through that choice normally (read dialogue, etc.)" size 14 color "#CCCCCC" hbox: spacing 10 text "5." size 16 color "#00FF00" text f"When ready for the next choice, press Alt+{dukeconfig.advance_key.upper()}" size 14 color "#CCCCCC" hbox: spacing 10 text "6." size 16 color "#00FF00" text "Repeat until you've experienced all choices!" size 14 color "#CCCCCC" # Controls frame: xfill True background "#112233" vbox: spacing 10 text "Keyboard Controls:" size 20 color "#FFFF00" hbox: spacing 20 vbox: text "Alt+M" size 16 color "#00FF00" text f"Alt+{dukeconfig.advance_key.upper()}" size 16 color "#00FF00" text f"Alt+{dukeconfig.emergency_exit_key.upper()}" size 16 color "#00FF00" text "Shift+M" size 16 color "#00FF00" vbox: text "Start Multi-Choice Mode" size 14 color "#CCCCCC" text "Advance to next choice" size 14 color "#CCCCCC" text "Emergency exit/reset" size 14 color "#CCCCCC" text "Open mod settings" size 14 color "#CCCCCC" # Visual indicators frame: xfill True background "#112233" vbox: spacing 10 text "Visual Indicators:" size 20 color "#FFFF00" text "• Blue Progress Box (top-left): Shows your progress through choices" size 14 color "#CCCCCC" text "• Red Emergency Exit Button: Available during multi-choice mode" size 14 color "#CCCCCC" text "• Choice Counter: Shows 'Choice X of Y completed'" size 14 color "#CCCCCC" text "• Status Messages: Keep you informed of what's happening" size 14 color "#CCCCCC" # Tips and tricks frame: xfill True background "#112233" vbox: spacing 10 text "Tips & Tricks:" size 20 color "#FFFF00" text "-> Take your time! There's no rush to advance to the next choice" size 14 color "#CCCCCC" text "-> You can still interact normally - click, read, enjoy the story" size 14 color "#CCCCCC" text "-> Use emergency exit if you get stuck or want to stop early" size 14 color "#CCCCCC" text "-> The mod works on ANY menu with multiple choices" size 14 color "#CCCCCC" text "-> Perfect for completionist playthroughs!" size 14 color "#CCCCCC" # Example scenario frame: xfill True background "#112233" vbox: spacing 10 text "Example Scenario:" size 20 color "#FFFF00" text "You encounter a choice menu with 3 options:" size 14 color "#CCCCCC" text " • 'Be friendly'" size 12 color "#AAAAAA" text " • 'Be neutral'" size 12 color "#AAAAAA" text " • 'Be hostile'" size 12 color "#AAAAAA" text "1. Press Alt+M or click the blue button" size 14 color "#CCCCCC" text "2. Game automatically selects 'Be friendly'" size 14 color "#CCCCCC" text "3. Read through the friendly dialogue/scenes" size 14 color "#CCCCCC" text f"4. Press Alt+{dukeconfig.advance_key.upper()} when ready" size 14 color "#CCCCCC" text "5. Game returns to menu and selects 'Be neutral'" size 14 color "#CCCCCC" text "6. Repeat until all 3 choices experienced!" size 14 color "#CCCCCC" # Troubleshooting frame: xfill True background "#112233" vbox: spacing 10 text "Troubleshooting:" size 20 color "#FFFF00" text "? Blue button not appearing?" size 14 color "#FFAAAA" text " • Try pressing Alt+M instead" size 12 color "#CCCCCC" text " • Check if mod is enabled in settings (Shift+M)" size 12 color "#CCCCCC" text "? Stuck in multi-choice mode?" size 14 color "#FFAAAA" text f" • Press Alt+{dukeconfig.emergency_exit_key.upper()} for emergency exit" size 12 color "#CCCCCC" text " • This will reset everything and stop the mod" size 12 color "#CCCCCC" text "? Not advancing to next choice?" size 14 color "#FFAAAA" text f" • Make sure to press Alt+{dukeconfig.advance_key.upper()} (not just {dukeconfig.advance_key.upper()})" size 12 color "#CCCCCC" text " • Wait for the choice to be fully experienced first" size 12 color "#CCCCCC" # Footer buttons hbox: spacing 20 xalign 0.5 textbutton "Open Settings": action [Hide("multichoice_guide"), Show("multichoice_settings", transition=dissolve)] text_size 16 background "#003366" hover_background "#004488" textbutton "Test Multi-Choice": action [Hide("multichoice_guide"), Call("multichoice_test_menu")] text_size 16 background "#006600" hover_background "#008800" textbutton "Close Guide": action Hide("multichoice_guide", transition=dissolve) text_size 16 background "#660000" hover_background "#880000" # Test menu for players to try the mod label multichoice_test_menu: scene bg black "Welcome to the Multi-Choice Test!" "This is a safe place to test the Multi-Choice Picker mod." "Try using Alt+M or clicking the blue button when the menu appears." menu test_choices: "What would you like to do?" "Option A - Friendly": "You chose the friendly option!" "This is what the friendly path looks like." "Pretty nice, right?" jump test_choices_end "Option B - Neutral": "You chose the neutral option!" "This is the neutral path." "Perfectly balanced, as all things should be." jump test_choices_end "Option C - Aggressive": "You chose the aggressive option!" "This is the aggressive path." "Sometimes you gotta be tough!" jump test_choices_end label test_choices_end: "That was one choice path!" "If you're using multi-choice mode, press Alt+N to experience the next choice." "If not, you can restart this test from the guide." return screen multichoice_settings(): modal True frame: xalign 0.5 yalign 0.5 xsize 650 textbutton "?" action Show("multichoice_guide", transition=dissolve) xalign 0.95 yalign 0.05 vbox: spacing 20 xfill True text "Multi-Choice Mod Settings - UNIVERSAL VERSION" xalign 0.5 size 22 color "#fff" hbox: xalign 0.5 text "Enable Multi-Choice:" size 18 textbutton ("On" if dukeconfig.multichoice_enabled else "Off"): action Function(toggle_multichoice) text_size 18 hbox: xalign 0.5 text "Universal Mode (Force on ALL menus):" size 18 textbutton ("On" if dukeconfig.force_universal else "Off"): action ToggleField(dukeconfig, "force_universal") text_size 18 hbox: xalign 0.5 text "Show Choice Numbers:" size 18 textbutton ("On" if dukeconfig.show_choice_numbers else "Off"): action ToggleField(dukeconfig, "show_choice_numbers") text_size 18 hbox: xalign 0.5 text "Show Progress Overlay:" size 18 textbutton ("On" if dukeconfig.show_progress_overlay else "Off"): action ToggleField(dukeconfig, "show_progress_overlay") text_size 18 hbox: xalign 0.5 text "Debug Mode:" size 18 textbutton ("On" if dukeconfig.debug else "Off"): action ToggleField(dukeconfig, "debug") text_size 18 vbox: xalign 0.5 text f"Advance Key: Alt+{dukeconfig.advance_key.upper()}" size 18 hbox: spacing 10 textbutton "N" action SetField(dukeconfig, "advance_key", "n") text_size 16 textbutton "Space" action SetField(dukeconfig, "advance_key", "SPACE") text_size 16 textbutton "Enter" action SetField(dukeconfig, "advance_key", "RETURN") text_size 16 textbutton "Right" action SetField(dukeconfig, "advance_key", "RIGHT") text_size 16 vbox: xalign 0.5 text f"Emergency Exit Key: Alt+{dukeconfig.emergency_exit_key.upper()}" size 18 hbox: spacing 10 textbutton "E" action SetField(dukeconfig, "emergency_exit_key", "e") text_size 16 textbutton "Q" action SetField(dukeconfig, "emergency_exit_key", "q") text_size 16 textbutton "Esc" action SetField(dukeconfig, "emergency_exit_key", "ESCAPE") text_size 16 textbutton "X" action SetField(dukeconfig, "emergency_exit_key", "x") text_size 16 null height 10 textbutton "Close": action Hide("multichoice_settings", transition=dissolve) xalign 0.5 text_size 20 frame: xalign 0.5 yalign 1.0 vbox: spacing 5 text "==Instructions==" size 18 color "#FFFF00" xalign 0.5 text "1. Works on ALL menus automatically!" size 14 color "#CCCCCC" text "2. Press Alt+M at any menu to activate Multi-Choice Mode" size 14 color "#CCCCCC" text "3. Experience each choice normally" size 14 color "#CCCCCC" text f"4. Press Alt+{dukeconfig.advance_key.upper()} when ready for the next choice" size 14 color "#CCCCCC" text f"5. Press Alt+{dukeconfig.emergency_exit_key.upper()} for emergency exit/reset" size 14 color "#CCCCCC" init 999 python: config.keymap['trigger_multichoice'] = ['alt_K_m'] config.keymap['open_multichoice_preference'] = ['shift_K_m'] config.keymap['advance_multichoice'] = [f'alt_K_{dukeconfig.advance_key}'] config.keymap['emergency_multichoice'] = [f'alt_K_{dukeconfig.emergency_exit_key}'] config.underlay.append(renpy.Keymap( trigger_multichoice=Function(multichoice_keymap), advance_multichoice=Function(advance_keymap), emergency_multichoice=Function(emergency_keymap), open_multichoice_preference=lambda: renpy.run(Show("multichoice_settings", transition=dissolve)), ))