701 lines
20 KiB
Text
701 lines
20 KiB
Text
# Copyright 2004-2019 Tom Rothamel <pytom@bishoujo.us>
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person
|
|
# obtaining a copy of this software and associated documentation files
|
|
# (the "Software"), to deal in the Software without restriction,
|
|
# including without limitation the rights to use, copy, modify, merge,
|
|
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
# and to permit persons to whom the Software is furnished to do so,
|
|
# subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be
|
|
# included in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
#
|
|
# This file contains the code for in-game Ren'Py error handling. It's a
|
|
# module (as opposed to a .rpy file) because that allows us to ensure
|
|
# that it is fully loaded or run before any other Ren'Py code runs.
|
|
|
|
|
|
init python in gui:
|
|
system_font = None
|
|
|
|
init python:
|
|
style._default = Style(None)
|
|
|
|
init python hide:
|
|
|
|
if renpy.loadable("gui.rpy") or renpy.loadable("gui.rpyc"):
|
|
config.screen_width, config.screen_height = 1280, 720
|
|
|
|
def init_system_styles():
|
|
if gui.system_font is not None:
|
|
style._default.font = gui.system_font
|
|
|
|
config.init_system_styles = init_system_styles
|
|
|
|
init label _errorhandling:
|
|
|
|
python in gui:
|
|
from store import config
|
|
|
|
def _scale(n):
|
|
return int(min(n * config.screen_width / 960, n * config.screen_height / 720))
|
|
|
|
_wide = (config.screen_width > (config.screen_height * 1.5))
|
|
|
|
style _default:
|
|
|
|
# Text properties
|
|
font "DejaVuSans.ttf"
|
|
language "unicode"
|
|
antialias True
|
|
size gui._scale(16)
|
|
color "#404040"
|
|
black_color (0, 0, 0, 255)
|
|
bold False
|
|
italic False
|
|
underline False
|
|
strikethrough False
|
|
kerning 0.0
|
|
drop_shadow None
|
|
drop_shadow_color (0, 0, 0, 255)
|
|
outlines [ ]
|
|
outline_scaling "step"
|
|
minwidth 0
|
|
text_align 0
|
|
justify False
|
|
text_y_fudge 0
|
|
first_indent 0
|
|
rest_indent 0
|
|
line_spacing 0
|
|
line_leading 0
|
|
line_overlap_split 0
|
|
layout "tex"
|
|
subtitle_width 0.9
|
|
slow_cps None
|
|
slow_cps_multiplier 1.0
|
|
slow_abortable False
|
|
hinting "auto"
|
|
adjust_spacing True
|
|
|
|
# Window properties
|
|
background None
|
|
xpadding 0
|
|
ypadding 0
|
|
xmargin 0
|
|
ymargin 0
|
|
xfill False
|
|
yfill False
|
|
xminimum 0
|
|
yminimum 0
|
|
|
|
# Placement properties
|
|
xpos None
|
|
ypos None
|
|
xanchor None
|
|
yanchor None
|
|
xmaximum None
|
|
ymaximum None
|
|
xoffset 0
|
|
yoffset 0
|
|
subpixel False
|
|
|
|
# Sound properties
|
|
activate_sound None
|
|
hover_sound None
|
|
|
|
# Box properties
|
|
spacing 0
|
|
first_spacing None
|
|
box_layout None
|
|
box_wrap False
|
|
box_wrap_spacing 0
|
|
box_reverse False
|
|
order_reverse False
|
|
xfit False
|
|
yfit False
|
|
|
|
# Button properties
|
|
focus_mask None
|
|
focus_rect None
|
|
keyboard_focus True
|
|
key_events False
|
|
hover_key_events True
|
|
|
|
# Bar properties
|
|
fore_bar Null()
|
|
aft_bar Null()
|
|
thumb None
|
|
thumb_shadow None
|
|
left_gutter 0
|
|
right_gutter 0
|
|
thumb_offset 0
|
|
unscrollable None
|
|
bar_invert False
|
|
bar_vertical False
|
|
|
|
# Viewport properties
|
|
clipping False
|
|
|
|
# Grid properties
|
|
xspacing None
|
|
yspacing None
|
|
|
|
init python:
|
|
# Temporarily, until the real styles can be defined.
|
|
style.default = style._default
|
|
style.image = style._default
|
|
style.fixed = style._default
|
|
|
|
##########################################################################
|
|
|
|
style _frame is _default:
|
|
background "#d0d0d0"
|
|
xpadding gui._scale(150 if gui._wide else 15)
|
|
ypadding gui._scale(15)
|
|
xfill True
|
|
yfill True
|
|
|
|
style _text is _default
|
|
style _fixed is _default
|
|
style _input is _default
|
|
|
|
style _hbox is _default:
|
|
box_layout 'horizontal'
|
|
|
|
style _vbox is _default:
|
|
box_layout 'vertical'
|
|
|
|
style _grid is _default
|
|
style _side is _default
|
|
|
|
style _drag is _default
|
|
|
|
style _viewport is _default:
|
|
xfill True
|
|
yfill True
|
|
|
|
style _button is _default:
|
|
ypadding gui._scale(6)
|
|
|
|
style _button_text is _default:
|
|
size gui._scale(22)
|
|
color "#468"
|
|
hover_color "#24c"
|
|
insensitive_color "#00000020"
|
|
|
|
style _small_button is _button
|
|
|
|
style _small_button_text is _button_text:
|
|
size gui._scale(16)
|
|
|
|
style _radio_button is _button:
|
|
selected_background Solid("#468", xsize=gui._scale(5), ysize=gui._scale(22), yalign=0.5)
|
|
selected_hover_background Solid("#24C", xsize=gui._scale(5), ysize=gui._scale(22), yalign=0.5)
|
|
left_padding gui._scale(15)
|
|
ypadding gui._scale(2)
|
|
|
|
style _label is _default:
|
|
top_margin gui._scale(10)
|
|
bottom_margin gui._scale(15)
|
|
|
|
style _label_text is _default:
|
|
size gui._scale(30)
|
|
kerning -1
|
|
|
|
style _bar is _default:
|
|
left_bar "#468"
|
|
hover_left_bar "#24C"
|
|
right_bar "#b0b0b0"
|
|
ysize gui._scale(20)
|
|
|
|
style _vbar is _default:
|
|
bottom_bar "#468"
|
|
hover_bottom_bar "#24C"
|
|
top_bar "#b0b0b0"
|
|
xsize gui._scale(20)
|
|
bar_vertical True
|
|
|
|
style _slider is _bar
|
|
style _vslider is _vbar
|
|
|
|
style _scrollbar is _default:
|
|
thumb "#808080"
|
|
hover_thumb "#a0a0a0"
|
|
base_bar "#b0b0b0"
|
|
unscrollable "hide"
|
|
ymaximum gui._scale(10)
|
|
|
|
style _vscrollbar is _default:
|
|
thumb "#808080"
|
|
hover_thumb "#a0a0a0"
|
|
base_bar "#b0b0b0"
|
|
unscrollable "hide"
|
|
xmaximum gui._scale(10)
|
|
bar_vertical True
|
|
bar_invert True
|
|
|
|
style _hyperlink is _default:
|
|
color "#468"
|
|
hover_color Color("#24C")
|
|
|
|
return
|
|
|
|
# Invokes _errorhanding when this is first loaded.
|
|
init:
|
|
call _errorhandling
|
|
|
|
# Early hyperlink support.
|
|
init python:
|
|
|
|
def _error_hyperlink_styler(target):
|
|
return style._hyperlink
|
|
|
|
def _error_hyperlink_function(target):
|
|
if target.startswith("http:") or target.startswith("https:"):
|
|
try:
|
|
import webbrowser
|
|
webbrowser.open(target)
|
|
except:
|
|
pass
|
|
|
|
if target.startswith("edit:"):
|
|
prefix, line, filename = target.split(":", 2)
|
|
line = int(line)
|
|
|
|
renpy.launch_editor([ filename ], line)
|
|
|
|
style._default.hyperlink_functions = (_error_hyperlink_styler, _error_hyperlink_function, None)
|
|
|
|
|
|
init python:
|
|
|
|
# The keymap we use before the real keymap is defined.
|
|
config.keymap = dict(
|
|
|
|
# Bindings present almost everywhere, unless explicitly
|
|
# disabled.
|
|
toggle_fullscreen = [ 'f', 'alt_K_RETURN', 'alt_K_KP_ENTER', 'K_F11' ],
|
|
reload_game = [ 'R' ],
|
|
quit = [ 'meta_q', 'alt_K_F4', 'alt_q' ],
|
|
iconify = [ 'meta_m', 'alt_m' ],
|
|
choose_renderer = [ 'G' ],
|
|
|
|
# Focus.
|
|
focus_left = [ 'K_LEFT' ],
|
|
focus_right = [ 'K_RIGHT' ],
|
|
focus_up = [ 'K_UP' ],
|
|
focus_down = [ 'K_DOWN' ],
|
|
|
|
# Button.
|
|
button_ignore = [ 'mousedown_1' ],
|
|
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER', 'K_SELECT' ],
|
|
|
|
# Viewport.
|
|
viewport_leftarrow = [ 'K_LEFT', 'repeat_K_LEFT' ],
|
|
viewport_rightarrow = [ 'K_RIGHT', 'repeat_K_RIGHT' ],
|
|
viewport_uparrow = [ 'K_UP', 'repeat_K_UP' ],
|
|
viewport_downarrow = [ 'K_DOWN', 'repeat_K_DOWN' ],
|
|
viewport_wheelup = [ 'mousedown_4' ],
|
|
viewport_wheeldown = [ 'mousedown_5' ],
|
|
viewport_drag_start = [ 'mousedown_1' ],
|
|
viewport_drag_end = [ 'mouseup_1' ],
|
|
viewport_pageup = [ 'K_PAGEUP', 'repeat_K_PAGEUP' ],
|
|
viewport_pagedown = [ 'K_PAGEDOWN', 'repeat_K_PAGEDOWN' ],
|
|
|
|
# These control the bar.
|
|
bar_activate = [ 'mousedown_1', 'K_RETURN', 'K_KP_ENTER', 'K_SELECT' ],
|
|
bar_deactivate = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER', 'K_SELECT' ],
|
|
bar_decrease = [ 'K_LEFT' ],
|
|
bar_increase = [ 'K_RIGHT' ],
|
|
|
|
# Null console, just in case.
|
|
console = [ ],
|
|
)
|
|
|
|
config.pad_bindings = {
|
|
"pad_leftshoulder_press" : [ "rollback", ],
|
|
"pad_lefttrigger_pos" : [ "rollback", ],
|
|
"pad_back_press" : [ "rollback", ],
|
|
|
|
"pad_guide_press" : [ "game_menu", ],
|
|
"pad_start_press" : [ "game_menu", ],
|
|
|
|
"pad_y_press" : [ "hide_windows", ],
|
|
|
|
"pad_rightshoulder_press" : [ "rollforward", ],
|
|
|
|
"pad_righttrigger_pos" : [ "dismiss", "button_select" ],
|
|
"pad_a_press" : [ "dismiss", "button_select" ],
|
|
"pad_b_press" : [ "button_alternate" ],
|
|
|
|
"pad_dpleft_press" : [ "focus_left", "bar_left" ],
|
|
"pad_leftx_neg" : [ "focus_left", "bar_left" ],
|
|
"pad_rightx_neg" : [ "focus_left", "bar_left" ],
|
|
|
|
"pad_dpright_press" : [ "focus_right", "bar_right" ],
|
|
"pad_leftx_pos" : [ "focus_right", "bar_right" ],
|
|
"pad_rightx_pos" : [ "focus_right", "bar_right" ],
|
|
|
|
"pad_dpup_press" : [ "focus_up", "bar_up" ],
|
|
"pad_lefty_neg" : [ "focus_up", "bar_up" ],
|
|
"pad_righty_neg" : [ "focus_up", "bar_up" ],
|
|
|
|
"pad_dpdown_press" : [ "focus_down", "bar_down" ],
|
|
"pad_lefty_pos" : [ "focus_down", "bar_down" ],
|
|
"pad_righty_pos" : [ "focus_down", "bar_down" ],
|
|
}
|
|
|
|
# Null translation function. This gets redefined once things start
|
|
# successfully.
|
|
def _(s):
|
|
return s
|
|
|
|
# This function is responsible for taking a traceback, and converting
|
|
# it to a string that can be shown with text.
|
|
def __format_traceback(s):
|
|
import re
|
|
|
|
lines = [ i.replace("{", "{{") for i in s.split("\n") ]
|
|
|
|
rv = [ ("{size=%d}" % gui._scale(22)) + lines[0] + "{/size}" ]
|
|
|
|
for i in lines[1:]:
|
|
i = re.sub(r'(File "(.*)", line (\d+))', r'{a=edit:\3:\2}\1{/a}', i)
|
|
rv.append(" " + i)
|
|
|
|
rv[1] = "{vspace=5}" + rv[1]
|
|
|
|
return "\n".join(rv)
|
|
|
|
|
|
def __format_parse_errors(s):
|
|
import re
|
|
|
|
rv = ""
|
|
|
|
lines = s.split("\n")
|
|
len_lines = len(lines)
|
|
|
|
ln = 0
|
|
|
|
while ln < len_lines:
|
|
line = lines[ln]
|
|
ln += 1
|
|
|
|
if ln < len_lines and lines[ln].endswith("^"):
|
|
highlight = len(lines[ln]) - 1
|
|
ln += 1
|
|
else:
|
|
highlight = -1
|
|
|
|
pos = 0
|
|
|
|
for c in line:
|
|
if pos == highlight:
|
|
rv += u"{color=#c00}\u2192{/color}"
|
|
highlight = -1
|
|
|
|
pos += 1
|
|
|
|
if c == "{":
|
|
rv += "{{"
|
|
else:
|
|
rv += c
|
|
|
|
if highlight > 0:
|
|
rv += u"{color=#c00}\u2190{/color}"
|
|
|
|
rv += "\n"
|
|
|
|
|
|
rv = re.sub(r'(File "(.*)", line (\d+))', r'{a=edit:\3:\2}\1{/a}', rv)
|
|
|
|
return rv
|
|
|
|
class _EditFile(Action):
|
|
def __init__(self, filename, line=1):
|
|
self.filename = filename
|
|
self.line = line
|
|
|
|
def __call__(self):
|
|
try:
|
|
renpy.launch_editor([ self.filename ], self.line, transient=1)
|
|
except:
|
|
pass
|
|
|
|
class _CopyFile(Action):
|
|
def __init__(self, filename, template):
|
|
self.filename = filename
|
|
self.template = template
|
|
|
|
def __call__(self):
|
|
import pygame.scrap
|
|
|
|
with open(self.filename, "rb") as f:
|
|
f.read(3) # skip the BOM.
|
|
s = self.template.format(f.read().decode("utf-8"))
|
|
|
|
s = s.replace("\n", "\r\n")
|
|
s = s.replace("\r\r", "\r")
|
|
|
|
pygame.scrap.put(pygame.SCRAP_TEXT, s.encode("utf-8"))
|
|
|
|
def __can_open_traceback():
|
|
return True
|
|
|
|
class __TooltipAction(object):
|
|
|
|
def __init__(self, tooltip, value):
|
|
self.tooltip = tooltip
|
|
self.value = value
|
|
|
|
def __call__(self):
|
|
if self.tooltip.value != self.value:
|
|
self.tooltip.value = self.value
|
|
renpy.restart_interaction()
|
|
|
|
def unhovered(self):
|
|
if self.tooltip.value != self.tooltip.default:
|
|
self.tooltip.value = self.tooltip.default
|
|
renpy.restart_interaction()
|
|
|
|
class __Tooltip(object):
|
|
def __init__(self, default):
|
|
self.value = default
|
|
self.default = default
|
|
|
|
def action(self, value):
|
|
return __TooltipAction(self, value)
|
|
|
|
class __XScrollValue(BarValue):
|
|
def __init__(self, viewport):
|
|
self.viewport = viewport
|
|
|
|
def get_adjustment(self):
|
|
w = renpy.get_widget(None, self.viewport)
|
|
if not isinstance(w, Viewport):
|
|
raise Exception("The displayable with id %r is not declared, or not a viewport." % self.viewport)
|
|
|
|
return w.xadjustment
|
|
|
|
def get_style(self):
|
|
return "scrollbar", "vscrollbar"
|
|
|
|
class __YScrollValue(BarValue):
|
|
def __init__(self, viewport):
|
|
self.viewport = viewport
|
|
|
|
def get_adjustment(self):
|
|
w = renpy.get_widget(None, self.viewport)
|
|
if not isinstance(w, Viewport):
|
|
raise Exception("The displayable with id %r is not declared, or not a viewport." % self.viewport)
|
|
|
|
return w.yadjustment
|
|
|
|
def get_style(self):
|
|
return "scrollbar", "vscrollbar"
|
|
|
|
class __ErrorQuit(Action):
|
|
"""
|
|
An action that quits with an error status.
|
|
"""
|
|
|
|
def __call__(self):
|
|
renpy.quit(status=1)
|
|
|
|
class __EnterConsole(Action):
|
|
"""
|
|
An action that enters the console if we can.
|
|
"""
|
|
|
|
def __call__(self):
|
|
try:
|
|
f = _console.enter
|
|
except:
|
|
return None
|
|
|
|
return f()
|
|
|
|
# This screen can be customized to add additional actions to the exception
|
|
# screen. It currently takes two positional parameters.
|
|
#
|
|
# * traceback_fn - a filename containing the exception text.
|
|
# * tt - a tooltip used for help text.
|
|
#
|
|
# For forward-compatibility, custom implmentations should use *args to ignore
|
|
# added arguments.
|
|
screen _exception_actions(traceback_fn, tt, *args):
|
|
|
|
textbutton _("Open"):
|
|
action _EditFile(traceback_fn)
|
|
hovered tt.action(_("Opens the traceback.txt file in a text editor."))
|
|
|
|
textbutton _("Copy BBCode"):
|
|
action _CopyFile(traceback_fn, u"[code]\n{}[/code]\n")
|
|
hovered tt.action(_("Copies the traceback.txt file to the clipboard as BBcode for forums like https://lemmasoft.renai.us/."))
|
|
|
|
textbutton __("Copy Markdown"):
|
|
action _CopyFile(traceback_fn, u"```\n{}```\n")
|
|
hovered tt.action(_("Copies the traceback.txt file to the clipboard as Markdown for Discord."))
|
|
|
|
|
|
|
|
python early in _errorhandling:
|
|
|
|
# These enable various error handling modes.
|
|
rollback = True
|
|
ignore = True
|
|
reload = True
|
|
console = True
|
|
|
|
# The screen that is used for error handling.
|
|
screen _exception:
|
|
modal True
|
|
zorder 1090
|
|
|
|
default tt = __Tooltip("")
|
|
default fmt_short = __format_traceback(short)
|
|
default fmt_full = __format_traceback(full)
|
|
|
|
frame:
|
|
style_group ""
|
|
|
|
has side "t c b":
|
|
spacing gui._scale(10)
|
|
|
|
side "c r":
|
|
xfill True
|
|
label _("An exception has occurred.") text_size gui._scale(40)
|
|
text "{size=-3}[config.version!q]\n[renpy.version_only!q]\n[renpy.platform!q]{/size}" text_align 1.0 yalign 0.5
|
|
|
|
viewport:
|
|
id "viewport"
|
|
child_size (4000, None)
|
|
mousewheel True
|
|
draggable True
|
|
scrollbars "both"
|
|
|
|
has vbox
|
|
|
|
text fmt_short substitute False
|
|
text fmt_full substitute False
|
|
|
|
vbox:
|
|
hbox:
|
|
spacing gui._scale(25)
|
|
|
|
if rollback_action and _errorhandling.rollback:
|
|
textbutton _("Rollback"):
|
|
action rollback_action
|
|
hovered tt.action(_("Attempts a roll back to a prior time, allowing you to save or choose a different choice."))
|
|
|
|
if ignore_action and _errorhandling.ignore:
|
|
textbutton _("Ignore"):
|
|
action ignore_action
|
|
|
|
if _ignore_action:
|
|
hovered tt.action(_("Ignores the exception, allowing you to continue."))
|
|
else:
|
|
hovered tt.action(_("Ignores the exception, allowing you to continue. This often leads to additional errors."))
|
|
|
|
if config.developer and not renpy.mobile:
|
|
if _errorhandling.reload:
|
|
textbutton _("Reload"):
|
|
action reload_action
|
|
hovered tt.action(_("Reloads the game from disk, saving and restoring game state if possible."))
|
|
|
|
if _errorhandling.console:
|
|
textbutton _("Console") :
|
|
action __EnterConsole()
|
|
hovered tt.action(_("Opens a console to allow debugging the problem."))
|
|
|
|
use _exception_actions(traceback_fn, tt)
|
|
|
|
vbox:
|
|
xfill True
|
|
|
|
textbutton _("Quit"):
|
|
xalign 1.0
|
|
action __ErrorQuit()
|
|
hovered tt.action(_("Quits the game."))
|
|
|
|
# Tooltip.
|
|
text tt.value
|
|
|
|
if config.developer and reload_action:
|
|
key "R" action reload_action
|
|
|
|
key "console" action __EnterConsole()
|
|
|
|
# The screen that is used for error handling.
|
|
screen _parse_errors:
|
|
modal True
|
|
zorder 1090
|
|
|
|
default tt = __Tooltip("")
|
|
default fmt_errors = __format_parse_errors(errors)
|
|
|
|
frame:
|
|
style_group ""
|
|
|
|
has side "t c b":
|
|
spacing gui._scale(10)
|
|
|
|
label _("Parsing the script failed.") text_size gui._scale(40)
|
|
|
|
viewport:
|
|
id "viewport"
|
|
child_size (4000, None)
|
|
mousewheel True
|
|
draggable True
|
|
scrollbars "both"
|
|
xfill True
|
|
yfill True
|
|
|
|
has vbox
|
|
|
|
text fmt_errors substitute False
|
|
|
|
vbox:
|
|
|
|
hbox:
|
|
spacing gui._scale(25)
|
|
|
|
textbutton _("Reload"):
|
|
action reload_action
|
|
hovered tt.action(_("Reloads the game from disk, saving and restoring game state if possible."))
|
|
|
|
textbutton _("Open"):
|
|
action _EditFile(error_fn)
|
|
hovered tt.action(_("Opens the errors.txt file in a text editor."))
|
|
|
|
textbutton _("Copy BBCode"):
|
|
action _CopyFile(error_fn, u"[code]\n{}[/code]\n")
|
|
hovered tt.action(_("Copies the errors.txt file to the clipboard as BBcode for forums like https://lemmasoft.renai.us/."))
|
|
|
|
textbutton __("Copy Markdown"):
|
|
action _CopyFile(error_fn, u"```\n{}```\n")
|
|
hovered tt.action(_("Copies the errors.txt file to the clipboard as Markdown for Discord."))
|
|
|
|
vbox:
|
|
xfill True
|
|
|
|
textbutton _("Quit"):
|
|
action __ErrorQuit()
|
|
hovered tt.action(_("Quits the game."))
|
|
xalign 1.0
|
|
|
|
# Tooltip.
|
|
text tt.value
|
|
|
|
if config.developer and reload_action:
|
|
key "R" action reload_action
|