Adding SMS system for internet messaging dialog and moving some files to other folders

This commit is contained in:
mirivlad 2024-04-12 17:16:28 +08:00
parent 641ae94f1d
commit 8e82b52f6f
37 changed files with 1481 additions and 72 deletions

4
game/.gitignore vendored
View File

@ -1,5 +1,5 @@
game/cache ./cache/
game/saves ./saves/
.vscode/ .vscode/
*.rpyc *.rpyc
*.txt *.txt

View File

@ -4,5 +4,126 @@
"**/*.rpa": true, "**/*.rpa": true,
"**/*.rpymc": true, "**/*.rpymc": true,
"**/cache/": true "**/cache/": true
},
"editor.tokenColorCustomizations": {
"textMateRules": [
{
"scope": "renpy.meta.plain",
"settings": {
"fontStyle": ""
}
},
{
"scope": "renpy.meta.i",
"settings": {
"fontStyle": "italic"
}
},
{
"scope": "renpy.meta.b",
"settings": {
"fontStyle": "bold"
}
},
{
"scope": [
"renpy.meta.u",
"renpy.meta.a"
],
"settings": {
"fontStyle": "underline"
}
},
{
"scope": "renpy.meta.s",
"settings": {
"fontStyle": "strikethrough"
}
},
{
"scope": "renpy.meta.i renpy.meta.b",
"settings": {
"fontStyle": "italic bold"
}
},
{
"scope": "renpy.meta.i renpy.meta.u",
"settings": {
"fontStyle": "italic underline"
}
},
{
"scope": "renpy.meta.i renpy.meta.s",
"settings": {
"fontStyle": "italic strikethrough"
}
},
{
"scope": "renpy.meta.b renpy.meta.u",
"settings": {
"fontStyle": "bold underline"
}
},
{
"scope": "renpy.meta.b renpy.meta.s",
"settings": {
"fontStyle": "bold strikethrough"
}
},
{
"scope": "renpy.meta.u renpy.meta.s",
"settings": {
"fontStyle": "underline strikethrough"
}
},
{
"scope": "renpy.meta.i renpy.meta.b renpy.meta.u",
"settings": {
"fontStyle": "italic bold underline"
}
},
{
"scope": "renpy.meta.i renpy.meta.b renpy.meta.s",
"settings": {
"fontStyle": "italic bold strikethrough"
}
},
{
"scope": "renpy.meta.i renpy.meta.u renpy.meta.s",
"settings": {
"fontStyle": "italic underline strikethrough"
}
},
{
"scope": "renpy.meta.b renpy.meta.u renpy.meta.s",
"settings": {
"fontStyle": "bold underline strikethrough"
}
},
{
"scope": "renpy.meta.i renpy.meta.b renpy.meta.u renpy.meta.s",
"settings": {
"fontStyle": "italic bold underline strikethrough"
}
},
{
"scope": "renpy.meta.color.text",
"settings": {
"foreground": "#ffffff"
}
},
{
"scope": "renpy.meta.color.#0f0",
"settings": {
"foreground": "#0f0"
}
},
{
"scope": "renpy.meta.color.#f00",
"settings": {
"foreground": "#f00"
}
}
]
} }
} }

950
game/7dots.rpy Normal file
View File

@ -0,0 +1,950 @@
# чтобы str() не вылетала на линуксе
define mystr = eval("lambda i: '%s' % i")
init -2000 python:
# деление с переводом в целочисленное для нового ренпая
def d2(x, d=2):
return int(x / d)
# префиксы спрайтов, теги в которых нужно делить не пробелами, а "_"
layered_prefixes = []
init:
# масштабирование
transform zoom(zoom=1):
zoom zoom
transform xzoom(zoom=1):
xzoom zoom
transform yzoom(zoom=1):
yzoom zoom
# прозрачность
transform alpha(alpha=1.):
alpha alpha
# размытость
transform blur(blur=4):
blur blur
# яркость
transform brightness(brightness=.25):
matrixcolor BrightnessMatrix(brightness)
# контраст
transform contrast(contrast=1.25):
matrixcolor ContrastMatrix(contrast)
# насыщенность
transform saturation(saturation=1.):
matrixcolor SaturationMatrix(saturation)
# подкрашивание картинок
transform color(color="#000"):
matrixcolor TintMatrix(color)
# спрайт цвета color1 переливается в color2
transform color2(color1="#fff", color2="#def", t=2):
matrixcolor TintMatrix(color1)
ease_quad t*.5 matrixcolor TintMatrix(color2)
ease_quad t*.5 matrixcolor TintMatrix(color1)
repeat
# силуэт цвета color
transform paint(color="#fff"):
matrixcolor TintMatrix(color) * InvertMatrix(1.) * TintMatrix("#000")
# силуэт цвета color1 переливается в color2
transform paint2(color1="#fff", color2="#def", t=2):
matrixcolor TintMatrix(color1) * InvertMatrix(1.) * TintMatrix("#000")
ease_quad t*.5 matrixcolor TintMatrix(color2) * InvertMatrix(1.) * TintMatrix("#000")
ease_quad t*.5 matrixcolor TintMatrix(color1) * InvertMatrix(1.) * TintMatrix("#000")
repeat
# относительное положение
transform xalign(xalign=.5):
xalign xalign
transform yalign(yalign=1.):
yalign yalign
transform align(xalign=.5, yalign=1.):
align (xalign, yalign)
# положение
transform xpos(xpos=.5):
xpos xpos
transform ypos(ypos=.0):
ypos ypos
transform pos(xpos=.5, ypos=.0):
pos (xpos, ypos)
# якорь
transform xanchor(xanchor=.5):
xanchor xanchor
transform yanchor(yanchor=1.):
yanchor yanchor
transform anchor(xanchor=.5, yanchor=1.):
anchor (xanchor, yanchor)
# отзеркаливание
transform hflip(xz=-1.):
xzoom xz
transform vflip(yz=-1.):
yzoom yz
# поворот против часовой
transform rotate(a=45, rotate_pad=True):
rotate_pad rotate_pad
rotate a
# повороты с перспективой
## вверх-вниз
transform turnx(x=45):
perspective True
matrixtransform RotateMatrix(x, 0, 0)
## влево-вправо
transform turny(y=45):
perspective True
matrixtransform RotateMatrix(0, y, 0)
## против часовой-по часовой
transform turnz(z=45):
perspective True
matrixtransform RotateMatrix(0, 0, z)
## по всем направлениям
transform turn(x=0, y=45, z=0):
perspective True
matrixtransform RotateMatrix(x, y, z)
# вырезание
transform crop(x=0, y=0, w=1., h=1.):
crop(x, y, w, h)
# подпрыгивание персонажа
transform leap(dt=.4, dyz=0.01, dxz=0.005):
yzoom 1.
easein dt*0.35 yzoom 1.+dyz xzoom 1.-dxz
easeout dt*0.35 yzoom 1. xzoom 1.
easein dt*0.15 yzoom 1.-dyz xzoom 1.+dxz
easeout dt*0.15 yzoom 1. xzoom 1.
# слева, но не у самого края
transform left2(xa=.35):
anchor (.5, 1.)
align(xa, 1.)
# справа, но не у самого края
transform right2(xa=.65):
anchor (.5, 1.)
align(xa, 1.)
# слева, за краем
transform left0():
anchor (1., 1.)
pos (.0, 1.)
# справа, за краем
transform right0():
anchor (1., 1.)
pos (1., 1.)
# сиськотряс
transform boobs(t=2):
yanchor 0 yzoom 1
easeout (t*.075) yzoom 1.05
easein (t*.1) yzoom .95
easeout (t*.125) yzoom 1.025
easein (t*.125) yzoom .975
easeout (t*.125) yzoom 1.01
easein (t*.15) yzoom .99
easeout (t*.15) yzoom 1.005
easein (t*.15) yzoom 1.
init -2 python:
# единоразовый dismiss
def skip_once():
renpy.end_interaction(True)
SkipOnce = renpy.curry(skip_once)
# остановить перемотку
def skip_stop():
renpy.config.skipping = None
SkipStop = renpy.curry(skip_stop)
# нужно, чтобы включить/отключить dismiss
can_dismiss = True
def dismiss_block():
global can_dismiss
return can_dismiss
# включить dismiss
def dismiss_on():
store.can_dismiss = True
DismissOn = renpy.curry(dismiss_on)
# отключить dismiss
def dismiss_off():
if config.say_allow_dismiss is None:
config.say_allow_dismiss = dismiss_block
store.can_dismiss = False
DismissOff = renpy.curry(dismiss_off)
# для отладки
def log(*args):
for i in args:
print(str(i))
Log = renpy.curry(log)
# эффект вспышки нужного цвета для смены фонов
def flash(color="#fff"):
return Fade(.25, 0, .75, color=color)
# показан ли экран (или один из экранов)
def has_screen(*args):
args = make_list(args)
for i in args:
if renpy.get_screen(i):
return True
return False
# директории для хранение звуков и музыки
audio_dir = config.play_channel
music_dir = "music"
# жёсткая пауза
def pause(t=1, hard=True):
renpy.pause(t, hard=hard)
# получить скриншот заданного размера, по умолчанию весь экран
def shot(w=config.screen_width, h=config.screen_height):
renpy.take_screenshot((w, h))
return FileCurrentScreenshot()
# сканируем папку музыки, на выходе - список мелодий без указанного расширения и папки
def get_music_list(folder=music_dir, ext="ogg"):
res = []
list = renpy.list_files()
for i in list:
if i.startswith(folder):
s = i[(len(folder) + 1):]
if s.endswith("." + ext):
res.append(s[:(-len(ext) - 1)])
return res
# сканируем папку, на выходе - список файлов нужного расширения
# по умолчанию расширения убираются
def get_file_list(folder="", ext="", hideext=True):
res = []
list = renpy.list_files()
for i in list:
if i.startswith(folder) or (not folder):
if folder:
s = i[(len(folder) + 1):]
else:
s = i
if ext:
if s.endswith("." + ext):
if hideext:
s = s[:(-len(ext) - 1)]
res.append(s)
else:
res.append(s)
if len(res) > 1:
# сортировка без учета регистра
res = sorted(res, key=lambda s: s.lower())
return res
# окно игры в центре экрана (вызывается из init)
def window_center():
import os
os.environ['SDL_VIDEO_CENTERED'] = '1'
# автоматическое объявление изображений (вызывается из init)
def images_auto(folders=["images"]):
config.automatic_images_minimum_components = 1
config.automatic_images = [' ', '_', '/']
config.automatic_images_strip = folders
# остановить перемотку
def stop_skip():
renpy.config.skipping = None
# строку в displayable
def img2disp(displayable):
if isinstance(displayable, (str, unicode)):
return renpy.displayable(displayable)
return displayable
# узнать текущие размеры изображения типа displayable
# например, после масштабирования и других операций
# не работает в разделе init
def get_size(displayable, w=config.screen_width, h=config.screen_height, st=0, at=0):
w, h = renpy.render(img2disp(displayable), w, h, st, at).get_size()
return int(w), int(h)
def get_width(displayable):
return get_size(displayable)[0]
def get_height(displayable):
return get_size(displayable)[1]
# если это не список, то сделать единственным элементом списка
def make_list(param):
if param is None:
return None
if not isinstance(param, list):
param = [param]
return param
# автоматическое объявление анимации
# описание функции Ani:
# автоматическое объявление картинки с анимацией,
# например есть кадры "images/neko%s.png",
# где %s - числа от 1 до 5, тогда объявляем анимацию так:
# image neko = Ani("neko", 5, 0.5, reverse = False)
# где:
# img_name - имя файла без номера (например, "neko")
# frames - количество кадров
# delay - пауза между кадрами в секундах
# если delay это кортеж, например (1, 2), то скорость будет меняться от 1 до 2 секунд
# loop - зациклить анимацию (по умолчанию включено)
# reverse - нужно ли проигрывание анимации в обратную сторону
# effect - эффект для смены кадров
# start - с какой цифры начинать отсчет кадров
# ext - расширение, если оно отлично от Null, то работаем с файлами,
# при ext=Null - с displayable (уже объявленными или даже измененными изображениями)
# так же можно добавлять любые стандартные для изображений параметры, типа масштабирования или прозрачности:
# image neko = Ani("neko", 5, 0.5, zoom=2.0, alpha=0.75)
def Ani(img_name, frames, delay=.1, loop=True, reverse=False, effect=Dissolve(.1, alpha=True), start=1, ext=None, **properties):
args = []
# если пауза переменная, то вычисляем ее шаг
if isinstance(delay, tuple):
d0 = delay[0]
d1 = delay[1]
f = (frames - 1)
if f <= 0:
dp = 0
else:
dp = (d1 - d0) * 1. / f
delay = d0
else:
dp = 0
# перебираем все кадры анимации
for i in range(start, start + frames):
if ext:
img = renpy.display.im.image(img_name + str(i) + "." + ext)
else:
img = img2disp(img_name + str(i))
img = Transform(img, **properties)
args.append(img)
if reverse or loop or (i < start + frames - 1):
args.append(delay)
delay += dp
# добавляем эффект для смены кадров
args.append(effect)
if reverse: # обратная анимация, если нужна
dp = -dp
delay += dp
for i in range(start + frames - 2, start, -1):
if ext:
img = renpy.display.im.image(img_name + str(i) + "." + ext)
else:
img = img2disp(img_name + str(i))
img = Transform(img, **properties)
args.append(img)
if loop or (i > start + 1):
args.append(delay)
delay += dp
args.append(effect)
return anim.TransitionAnimation(*args)
# показать фон с именем bg указанного цвета color
def bg(color="#000", bg="bg"):
renpy.scene()
renpy.show(bg, what=img2disp(color))
# меняем стандартное время всех или некоторых эффектов для появления/исчезновения спрайтов
def move_time(delay=.5, effects=["move", "ease"]):
effects = make_list(effects)
for i in effects:
define.move_transitions(i, delay)
import datetime
# для цифровых часиков (не менять, не вызывать)
clock_properties = None
def show_digital_clock(st, at):
cur_time = datetime.datetime.now().strftime("%H:%M:%S")
img = Text(cur_time, **clock_properties)
return img, .25
# создать цифровые часики с любым именем картинки (только в init!):
# $ create_clock("digital_clock", size=48, color="#fff8", outlines=[(2, "#0008", 0, 0)], align=(.05, .05))
# применение - на любом экране screen:
# add "digital_clock"
# или в виде спрайта:
# show digital_clock
def create_clock(name, **properties):
global clock_properties
clock_properties = properties
renpy.image(name, DynamicDisplayable(show_digital_clock))
# показать экран на слое "мастер",
# чтобы он не исчезал, когда прячем интерфейс
def show_s(screen, *arg, **kwarg):
renpy.show_screen(screen, _layer="master", *arg, **kwarg)
# убрать экран со слоя "мастер"
def hide_s(screen, **kwarg):
renpy.hide_screen(screen, layer="master", **kwarg)
# добавляем неубирающийся по hide_interface слой
if not "forever" in config.layers:
config.layers.insert(config.layers.index("screens"), "forever")
# показать неубирающийся по нажатию "h" экран
def show_forever(screen):
renpy.show_screen(screen, _layer="forever")
# спрятать неубирающийся по нажатию "h" экран
def hide_forever(screen):
renpy.hide_screen(screen, layer="forever")
# получить английское название времени суток
# если не указывать время в часах,
# то будет взято системное время
# можно задать начало утра, дня, вечера и ночи в часах от 0 до 23
def time_of_day(hours=None, morning=7, day=11, evening=18, night=23):
if hours is None:
hours = int(datetime.datetime.now().strftime("%H"))
res = "night" # по умолчанию ночь
# границы любого времени суток можно поменять
if (hours >= morning) and (hours <= day):
res = "morning"
if (hours > day) and (hours <= evening):
res = "day"
if (hours > evening) and (hours < night):
res = "evening"
return res
# словарь цветов для времен суток
color_filters = {"morning": "#8404", "day": "#0000", "evening": "#0484", "night": "#000b"}
# получить цвет фильтра, соответствующий времени суток
def color_of_day(hours=None):
return color_filters[time_of_day(hours)]
# удалить все сохранения
def delete_saves_now():
all = renpy.list_saved_games(fast=True)
for i in all:
renpy.unlink_save(i)
renpy.restart_interaction()
DeleteSavesNow = renpy.curry(delete_saves_now)
# удаление всех сохранений с запросом подтверждения
def delete_saves(confirm=True):
if confirm:
layout.yesno_screen(message=_("Удалить все сохранения?"), yes=DeleteSavesNow(), no=NullAction())
else:
delete_saves_now()
DeleteSaves = renpy.curry(delete_saves)
# очистить постоянные данные и сохранения
def delete_data_now():
# удаление сохранений
delete_saves(False)
# удаление ачивок
# achievement.clear_all()
# achievement.sync()
# удаление постоянных данных
persistent._clear(progress=True)
# выход из игры
Quit(confirm=False)()
DeleteDataNow = renpy.curry(delete_data_now)
# удаление всех данных и сохранений с запросом подтверждения
def delete_data(confirm=True):
if confirm:
layout.yesno_screen(message="Удалить все данные?\nИгра будет закрыта.", yes=DeleteDataNow(), no=NullAction())
DeleteData = renpy.curry(delete_data)
# действие - продолжить игру оттуда, где закончили
# если загружать пока нечего, то кнопка неактивна
# textbutton _("Продолжить игру") action Continue()
class Continue(Action, DictEquality):
def __call__(self):
FileLoad(1, confirm=False, page="auto", newest=True)()
# кликабельность кнопки
def get_sensitive(self):
return FileLoadable(1, page="auto")
# объявлена ли картинка с именем name
def has_image(name):
for i in renpy.display.image.images:
# такая конструкция позволяет исключить пустые теги
if name == " ".join(" ".join(i).split()):
return True
return False
# проверить существование нескольких изображений через запятую
def has_images(*args):
res = True
for i in args:
# вместо какой-то картинки может быть список или кортеж
if isinstance(i, (list, dict)):
res = res & has_images(i)
else:
res = res & has_image(i)
return res
# задан ли курсор с таким именем
def has_mouse(mouse):
if config.mouse:
if mouse in config.mouse.keys():
return True
return False
# рандомный элемент из параметров на входе
# просто сокращаем писанину
def rnds(*args):
return renpy.random.choice(args)
# рандомное целое число в заданных пределах
# второй предел НЕ включительно, как в питоне
# (i_to можно не указывать, тогда максимум берется из i_from)
def rnd(i_from=0, i_to=None):
if i_to is None:
i_to = i_from
i_from = 0
return renpy.random.randint(int(i_from), int(i_to - 1))
# рандомное дробное число в заданных пределах
# (f_to можно не указывать, тогда максимум берется из f_from)
def rndf(f_from=0, f_to=None):
if f_to is None:
f_to = f_from
f_from = .0
return f_from + renpy.random.random() * (f_to - f_from)
# канал для зацикленного эффекта
renpy.music.register_channel("effect", "sfx", loop=True)
# зацикленный звуковой эффект, не музыка
def sfxplay(name, channel="effect", loop=True, fadein=1, fadeout=1, ext="ogg"):
if name:
renpy.music.play(audio_dir + "/" + name + "." + ext, channel=channel, loop=loop, fadein=fadein, fadeout=fadeout)
# костыли для звуков и музыки - сокращают писанину
# можно запускать музыку или звуки, не указывая папки и расширения
# по умолчанию для музыки music/*.ogg
# по умолчанию для звуков audio/*.ogg
# запустить музыку или плейлист
def mplay(mname, fadein=1, fadeout=1, loop=True, channel="music", ext="ogg"):
list = []
mname = make_list(mname)
for i in mname:
list.append(music_dir + "/" + i + "." + ext)
renpy.music.play(list, channel=channel, loop=loop, fadein=fadein, fadeout=fadeout)
# запустить музыку или случайно перемешанный плейлист
def rndplay(mname, fadein=1, fadeout=1, loop=True, channel="music", ext="ogg"):
list = make_list(mname)
if len(list) > 1:
renpy.random.shuffle(list)
mplay(list, fadein, fadeout, loop, channel, ext)
# перезапустить музыку, даже если уже играет она же
def mreplay(mname, fadein=1, fadeout=1, loop=True, channel="music", ext="ogg"):
new_fn = music_dir + "/" + mname + "." + ext
renpy.music.play(new_fn, channel=channel, loop=loop, fadein=fadein, fadeout=fadeout)
# запустить музыку для имени файла и пути
def fnplay(new_fn, fadein=1, fadeout=1, channel="music"):
old_fn = renpy.music.get_playing()
if old_fn != new_fn and new_fn:
renpy.music.play(new_fn, channel=channel, loop=True, fadein=fadein, fadeout=fadeout)
# последняя сохраненная мелодия
last_music_fn = ""
# сохранить в памяти играющую мелодию
def msave():
global last_music_fn
last_music_fn = renpy.music.get_playing()
# восстановить игравшую при сохранении мелодию
def mrestore(fadein=1, fadeout=1, channel="music"):
if last_music_fn:
fnplay(last_music_fn, fadein=fadein, fadeout=fadeout, channel=channel)
# воспроизвести звук для канала audio, который поддерживает многопоточность
def splay(mname, fadein=0, fadeout=0, channel=config.play_channel, ext="ogg"):
if mname:
mname = make_list(mname)
lst = []
for i in mname:
lst.append(audio_dir + "/" + i + "." + ext)
renpy.play(lst, channel=channel, fadein=fadein, fadeout=fadeout)
# воспроизвести звук
def sndplay(mname, fadein=0, fadeout=0, channel="sound", ext="ogg"):
if mname:
mname = make_list(mname)
lst = []
for i in mname:
lst.append(audio_dir + "/" + i + "." + ext)
renpy.play(lst, channel=channel, fadein=fadein, fadeout=fadeout)
# голос
def vplay(mname, fadein=0, fadeout=0, channel="voice", ext="ogg"):
if mname:
renpy.play("voices/" + mname + "." + ext, channel=channel, fadein=fadein, fadeout=fadeout)
# остановить звук
def sstop(fadeout=None, channel='audio'):
renpy.music.stop(channel=channel, fadeout=fadeout)
# остановить звук
def sndstop(fadeout=0, channel='sound'):
renpy.music.stop(channel=channel, fadeout=fadeout)
# остановить музыку
def mstop(fadeout=1, channel='music'):
renpy.music.stop(channel=channel, fadeout=fadeout)
# остановить зацикленный эффект
def sfxstop(fadeout=1, channel='effect'):
renpy.music.stop(channel=channel, fadeout=fadeout)
# превращаем функции в action для экранов screen
SPlay = renpy.curry(splay)
SFXPlay = renpy.curry(sfxplay)
SFXStop = renpy.curry(sfxstop)
MPlay = renpy.curry(mplay)
FNPlay = renpy.curry(fnplay)
VPlay = renpy.curry(vplay)
SStop = renpy.curry(sstop)
MStop = renpy.curry(mstop)
import re
# получить словарь с найденными в строке тегами и их значением
def get_tags(text, prefix='#'):
res = {}
# выуживаем все теги ремарок
tags = re.findall('{' + prefix + '([^}]+)}', text)
# перебираем полученные теги
for i in tags:
parts = i.split('=')
if len(parts) > 0:
key = parts[0].strip()
val = None
if len(parts) > 1:
val = parts[1]
# добавляем тэг и его значение в словарь
res[key] = val
# возвращаем значения тэгов в виде словаря
return res
# убрать все тэги из строки
def del_tags(txt, prefix='#'):
if txt:
return re.sub(r'{' + prefix + '([^}]+)}', '', txt)
else:
return txt
# получить тэги и вернуть их в виде списка строк
def get_tags_str(text, prefix='#'):
# выуживаем все теги ремарок
return re.findall('{' + prefix + '([^}]+)}', text)
# разделить строку на две части - до и после знака равно
# (или другого разделителя) и убрать пробелы вокруг этих частей
def get_key_val(text, sep='='):
txt = text.split(sep, 1)
val, key = None, None
if len(txt) > 0:
key = txt[0].strip()
if len(txt) > 1:
val = txt[1].strip()
return key, val
# поиск в строке значения невидимого читателю тега
# пример использования:
# $ text = "Текст текст {#image=logo.png} текст."
# $ img = get_tag(text, 'image')
# на выходе в img будет logo.png
def get_tag(text, tag, default=None, prefix='#'):
tag = tag.strip()
tags = get_tags(text, prefix)
if tag in tags.keys():
return tags[tag]
return None
# есть ли тэг в строке
def have_tag(text, tag, prefix='#'):
return tag in get_tags(text, prefix).keys()
# нужно включить автоматические сохранения, чтобы работала Continue
config.has_autosave = True
# список спрайтов на экране (не только тегов)
def get_showing_sprites(layer='master'):
images = []
tags = renpy.get_showing_tags(layer)
for i in tags:
tag = i
atrs = renpy.get_attributes(i, layer)
for a in atrs:
tag += " " + a
images.append(tag)
return images
# найти на экране спрайт, содержащий тег
def get_sprite_by_tag(tag, layer='master'):
if tag:
# если среди всех спрайтов на экране
images = get_showing_sprites(layer)
for i in images:
# есть тот, что содержит нужный тэг
if str(tag) in str(i):
return str(i)
# если на экране нет говорящего персонажа
return None
# получить параметры спрайта
def get_sprite_bounds(tag, layer='master'):
# если не нашли спрайт
x, y, w, h = -1, -1, -1, -1
# ищем спрайт на экране
i = get_sprite_by_tag(tag, layer)
# если спрайт на экране
if i:
x, y, w, h = renpy.get_image_bounds(i, layer=layer)
return int(x), int(y), int(w), int(h)
# полное копирование
import copy as dcopy
def copy(*args):
return dcopy.deepcopy(*args)
# БЛОК ДЛЯ РАБОТЫ С ВРЕМЕНАМИ СУТОК
# как пользоваться:
# 1) если есть нужные рисунки, то назвать файлы по временам суток:
# bg_street_night, bg_street_morning и т.д. по списку
# в противном случае будет перекрашиваться основная картинка
# например, "bg street day" или скопированная в неё "bg street"
# первый суффикс в списке времён суток можно и не указывать: bg street
# 2) воспользоваться автоматическим объявлением спрайтов в блоке init:
# $ images_auto()
# 3) указать перфиксы спрайтов, которые будут зависеть от времени суток
# например, чтобы все фоны и все спрайты eileen и pytom зависели от времени суток:
# daytime_prefix = ["bg", "eileen", "pytom"]
# 4) при необходимости задать список времён суток, по умолчанию он:
# alldaytime = ["day", "night"]
# 5) в скрипте просто задавать время суток $ setdaytime("night")
# или переключение на следующее по списку (зациклено): $ setdaytime()
# спрайты на экране сразу поменяются
init:
# настройки нового освещения, на входе сразу матрица
transform daytime_light(matrix):
matrixcolor matrix
transform daytime_empty():
pass
init -99 python:
# список допустимых времён суток
# "night" - обязательно!
alldaytime = ["day", "night"]
# список префиксов-меток для динамических спрайтов,
# освещение которых будет зависеть от времени суток в curdaytime
# например, все фоны и пара героев:
# daytime_prefix = ["bg", "eileen", "pytom"]
# к таким именам будет добавлен суффикс из daytime_suffix
# а потом они будут превращены в динамические уже со своими именами без суффикса
daytime_prefix = []
# настройки каждого времени суток для перекрашивания фонов и спрайтов
# (color, brightness, saturation, contrast)
day_bg_attrs = ("#000", 0, 1, 1)
day_attrs = ("#000", 0, 1, 1)
evening_bg_attrs = ("#9af", -.2, .8, 1)
evening_attrs = ("#9af", 0, .9, 1)
night_bg_attrs = ("#9af", -.475, .375, .575)
night_attrs = ("#9af", -.1, .375, 1)
morning_bg_attrs = ("#fca", .1, 1, 1)
morning_attrs = ("#fca", .1, 1, 1)
# по умолчанию первое в списке время суток
curdaytime = alldaytime[0]
# суффикс для динамических спрайтов - первое в списке время суток
daytime_suffix = alldaytime[0]
# здесь будут храниться имена изменённых спрайтов, зависимых от curdaytime
daytime_suffixed = []
# префикс-метка для фонов
bg_prefix = "bg"
# переключение времени суток на новое или на следующее, если не указывать, на какое
def setdaytime(newdaytime=None, effect=dissolve):
if newdaytime is None:
# если новое время суток == None,
# то переключаемся на следующее в списке (по кругу)
i = alldaytime.index(curdaytime) + 1
if i >= len(alldaytime):
i = 0
newdaytime = alldaytime[i]
if effect:
renpy.show("black", tag="daytimeblack")
renpy.with_statement(effect)
store.curdaytime = newdaytime
if effect:
renpy.hide("daytimeblack")
renpy.with_statement(effect)
# получить трансформ с матрицей для ночного освещения
# по умолчанию для спрайта, True - для фона
def atdaytime(bg=False):
if bg:
key = "_" + bg_prefix + "_"
else:
key = "_"
key = curdaytime + key + "attrs"
attrs = ("#000", 0, 1, 0)
if key in globals().keys():
color, brightness, saturation, contrast = globals()[key]
matrix = BrightnessMatrix(brightness) * ContrastMatrix(contrast) * TintMatrix(color) * SaturationMatrix(saturation)
return daytime_light(matrix)
return daytime_empty()
# БЛОК ДЛЯ ДОПОЛНЕННОГО АВТООБЪЯВЛЕНИЯ СПРАЙТОВ
# стандартное автообъявление картинок, но с webp
# плюс не разбиваются на теги имена, предназначенные для LayerdImage
# вместо этого объединяются через нижний минус "_"
# префиксы, по которым определяются такие имена берутся из layered_prefixes
# выполняется после всего остального
init 1900 python hide:
def create_automatic_images():
seps = config.automatic_images
if seps is True:
seps = [ ' ', '/', '_' ]
for dir, fn in renpy.loader.listdirfiles():
if fn.startswith("_"):
continue
# только .png и .jpg и .jpeg и .webp
if not (fn.lower().endswith(".png") or fn.lower().endswith(".jpg") or fn.lower().endswith(".jpeg") or fn.lower().endswith(".webp")):
continue
# убираем расширения и заменяем слеши.
if fn.lower().endswith(".jpeg") or fn.lower().endswith(".webp"):
shortfn = fn[:-5].replace("\\", "/")
else:
shortfn = fn[:-4].replace("\\", "/")
# делим строку на части
name = ( shortfn, )
for sep in seps:
name = tuple(j for i in name for j in i.split(sep))
# выбрасываем имя папок из тегов
while name:
for i in config.automatic_images_strip:
if name[0] == i:
name = name[1:]
break
else:
break
# для проверки префиксов и суффиксов после
# возможной склейки тегов
prefix = name[0]
suffix = name[len(name) - 1]
# убираем суффикс, если это первое время суток
if suffix == daytime_suffix:
name = name[:-1]
# имя без суффикса
name0 = name
# теги одной строкой
sname = " ".join(name)
# не делим на части имена, которые начинаются с префиксов для LayeredImage
layered = False
if prefix in layered_prefixes:
name = "_".join(name)
sname = name
layered = True
# добавляем суффикс по умолчанию, если начинается
# с префикса из списка меток для динамических спрайтов,
# которые будут менять освещение в зависимости от времени суток
if prefix in daytime_prefix:
# нельзя автоматизировать файлы с суффиксами времён суток,
# потому что они станут частью автоматизированного спрайта
if not suffix in alldaytime[1:]:
if not sname in daytime_suffixed:
# запоминаем имя изменённого спрайта
store.daytime_suffixed.append(sname)
# добавляем суффикс
if layered:
name = name + " " + daytime_suffix
else:
name = (*name, daytime_suffix)
# игнорируем, если уже создана одноимённая копия с суффиксом
if name0 in daytime_suffixed:
continue
# игнорируем, если не набирается указанное в переменной количество тегов
if len(name) < config.automatic_images_minimum_components:
continue
# игнорируем, если такой спрайт уже есть
if name in renpy.display.image.images:
continue
# объявляем спрайт
renpy.image(name, fn)
# если заданы параметры, то объявляем спрайты автоматом
if config.automatic_images:
create_automatic_images()
# функция для динамического освещения спрайтов и фонов
def def_daytime(st, at, img):
# ищем картинку для текущего времени суток
new = img + " " + curdaytime
if has_image(new):
return new, None
# если не нашли, то берем основную
new = img + " " + daytime_suffix
# и перекрашиваем в соответствии с настройками
if img.startswith(bg_prefix):
return At(new, atdaytime(True)), None
return At(new, atdaytime()), None
# если заданы параметры, то создаём динамически освещённые спрайты
if len(daytime_suffixed) > 0:
# перебираем все спрайты, для которых нужна реакция на освещение
for i in daytime_suffixed:
# создаём такие спрайты
renpy.image(i, DynamicDisplayable(def_daytime, i))

BIN
game/audio/new_message.mp3 Normal file

Binary file not shown.

BIN
game/audio/sms.ogg Normal file

Binary file not shown.

26
game/ends.rpy Normal file
View File

@ -0,0 +1,26 @@
label fast_end1:
play music "Anemoia - System #1.mp3" loop volume 0.5
nvl clear
root_mind "На удивление, я заснул в этот раз быстро и крепко."
sysmsg "System will be shutdown..."
sysmsg "...3"
sysmsg "...2"
sysmsg "...1"
sysmsg "Это была самая быстра концовка на Диком Западе!"
return
label fast_end2:
play music "Anemoia - System #1.mp3" loop volume 0.5
nvl clear
root_mind "Ну и не очень то и хотелось. Сами за копейки вкалывайте. Я лучше попробую спать лечь."
sysmsg "System will be shutdown..."
sysmsg "...3"
sysmsg "...2"
sysmsg "...1"
sysmsg "Суслик пошел домой и... никого не встретил!"
return

View File

@ -59,13 +59,13 @@ define gui.interface_text_color = '#ffffff'
## Шрифты и их размеры ######################################################### ## Шрифты и их размеры #########################################################
## Шрифт, используемый внутриигровым текстом. ## Шрифт, используемый внутриигровым текстом.
define gui.text_font = "terminus.ttf" define gui.text_font = "gui/fonts/terminus.ttf"
## Шрифт, используемый именами персонажей. ## Шрифт, используемый именами персонажей.
define gui.name_text_font = "terminus.ttf" define gui.name_text_font = "gui/fonts/terminus.ttf"
## Шрифт, используемый текстом вне игры. ## Шрифт, используемый текстом вне игры.
define gui.interface_text_font = "terminus.ttf" define gui.interface_text_font = "gui/fonts/terminus.ttf"
## Размер нормального текста диалога. ## Размер нормального текста диалога.
define gui.text_size = 33 define gui.text_size = 33

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
game/images/emoji/rofl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
game/images/emoji/sweat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
game/images/phone mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

BIN
game/images/phone skin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
game/images/phone top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
game/images/phone/mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

BIN
game/images/phone/skin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
game/images/phone/top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

23
game/init.rpy Normal file
View File

@ -0,0 +1,23 @@
#Инициализирующие параметры. Переменные используемые в игре и т.п.
# Определение персонажей игры.
define sysmsg = Character('sysmsg', color="#c83333")
define root = Character("[root]", color="#2ad3fd")
define root_mind = Character("{i}[root]{/i}", kind=nvl, color="#00ffea")
define unknown_char = Character("Unknown Contact", color="#09ff00")
define mystic_char = Character("╨п╨╕╨╖╨▒╤Г╨┤╤Г╤Й╨╡╨│╨╛", color="#ff8800")
#Определение сцен
image root_room = "Scenes/root_room.jpg"
#define menu = nvl_menu
# Вместо использования оператора image можете просто
# складывать все ваши файлы изображений в папку images.
# Например, сцену bg room можно вызвать файлом "bg room.png",
# а eileen happy — "eileen happy.webp", и тогда они появятся в игре.
$ sms_trans = [align(.15, .5), rotate(0, False)]
#Переменные
#Прочитал ли персонаж сообщение с предупреждением не запускать скрипт
define mystic_read = 0

View File

@ -95,6 +95,8 @@ style frame:
## https://www.renpy.org/doc/html/screen_special.html#say ## https://www.renpy.org/doc/html/screen_special.html#say
screen say(who, what): screen say(who, what):
on "show" action SMSAdd(what)
style_prefix "say" style_prefix "say"
window: window:

Binary file not shown.

View File

@ -1,19 +1,4 @@
# Вы можете расположить сценарий своей игры в этом файле. # Игра начинается здесь:
# Определение персонажей игры.
define sysmsg = Character('sysmsg', color="#c83333")
define root = Character("[root]", color="#2ad3fd")
define root_mind = Character("{i}[root]{/i}", kind=nvl, color="#00ffea")
define unknown_char = Character("???", color="#09ff00")
define mystic_char = Character("╨п╨╕╨╖╨▒╤Г╨┤╤Г╤Й╨╡╨│╨╛", color="#ff8800")
image root_room = "Scenes/root_room.jpg"
#define menu = nvl_menu
# Вместо использования оператора image можете просто
# складывать все ваши файлы изображений в папку images.
# Например, сцену bg room можно вызвать файлом "bg room.png",
# а eileen happy — "eileen happy.webp", и тогда они появятся в игре.
# Игра начинается здесь:
label start: label start:
play music "UNIVERSFIELD - Orion Nebula 2.mp3" loop volume 0.5 play music "UNIVERSFIELD - Orion Nebula 2.mp3" loop volume 0.5
scene console scene console
@ -34,21 +19,6 @@ label start:
scene root_room scene root_room
show monitor show monitor
# root_mind "Рабочий вечер после трудного рабочего дня. Что может быть лучше? Только рабочий вечер с кружкой горячего кофе."
# root_mind "Я заварил свой любимый, хоть и дешевый кофе в кофеварке. Некоторые бы подумали, что при моей зарплате, я бы мог позволить себе и подороже."
# root_mind "Однако я пристрастился к этому ужасному вкусу довольно давно, и все остальные сорта кофе казались блеклыми. Ну и ладно. Главное что работу стимулятора мой напиток выполнял исправно."
# root_mind "Я привычно запустил подключение к серверу компании на которую работал уже больше полугода. Пробежавшись быстро по задачам, запустил деплой, открыл редактор и начал писать скрипт."
# root_mind "Не сказать, чтобы мне уж так нужна была эта подработка, однако глупо отказываться от денег, когда есть силы работать, а вечера занять абсолютно нечем"
# root_mind "Друзей у меня было не много, да и с теми что были я встречался довольно редко. Девушки хоть и бывали, но как-то всегда не серьезно и непостоянно. Игры давно приелись, хобби тоже."
# root_mind "Скучные, долгие вечера... и скучные долгие ночи..."
# root_mind "Бессоница пришла ко мне пару месяцев назад. Сначала это были задержки за подработкой до часу ночи. Потом до двух. Незаметно для меня это превратилось в засиживание до 5 утра."
# root_mind "И подъем в семь на основную работу. Я с некоторым внутренним удивлением наблюдал за изменениями в моем режиме сна, но ничего не предпринимал для того чтобы что-то изменить."
# root_mind "Дни становились тягучее и тусклее. Час забытия после основной работы и затем вечер за подработкой."
# root_mind "А затем, даже ощущая бесконечную усталось, я закрывал рабочие задачи и открывал интернет. Смотрел на ютубе ролики о науке, новости о политике, читал айтишные форумы и блоги."
# root_mind "В пять утра я начинал зевать и благодаря всех богов за эту милость падал в кровать и проваливался в черное забытье"
# root_mind "Утро я помнил плохо и снова блеклый день, и жизнь безостановочно проходящая мимо..."
# root_mind "Возможно так бы дальше все и продолжалось. Но все изменилось когда в мессенджер пришло сообщение с неизвестного аккаунта."
root_mind """ root_mind """
Рабочий вечер после трудного рабочего дня. Что может быть лучше? Только рабочий вечер с кружкой горячего кофе. Рабочий вечер после трудного рабочего дня. Что может быть лучше? Только рабочий вечер с кружкой горячего кофе.
@ -116,18 +86,46 @@ label start:
label game: label game:
play music "UNIVERSFIELD - Space Journey Through Nebulae and Galaxy.mp3" loop volume 0.5 play music "UNIVERSFIELD - Space Journey Through Nebulae and Galaxy.mp3" loop volume 0.5
unknown_char "Привет. Дали твои контакты, сказали ты админишь сервера по удаленке. Есть пара задач, по оплате - какая ставка у тебя за час работы?" # Переписка с неизвестным от Палармо
$ sms_unknown = SMSL(_("Неизвестный контакт"))
# это слова получателя
$ sms_r = SMSR(_("Я"))
# сервисные сообщения
$ sms_c = SMSC(_("Системное сообщение"))
# имя абонента
$ sms_who = _("Неизвестный")
# цвет имени в шапке
$ sms_who_color = "#fff"
$ sms_show(_("Неизвестный контакт"), True)
with dissolve
$ now = time_now()
sms_c "Сегодня 03:16"
sms_unknown "[root], привет! Дали твои контакты, сказали ты админишь сервера по удаленке. Есть пара задач, по оплате - какая ставка у тебя за час работы?"
python: python:
rubperhour = renpy.input("Час вашей работы стоит:", length=32) rubperhour = renpy.input("Час вашей работы стоит:", length=32)
rubperhour = int(rubperhour.strip()) rubperhour = int(rubperhour.strip())
if not rubperhour: if not rubperhour:
rubperhour = 3000 rubperhour = 3000
sms_r "[rubperhour] рублей за час. Оплата минимум за час, даже если делов на 5 минут."
if rubperhour > 5000: if rubperhour > 5000:
unknown_char "Ух... Извини что побеспокоил. Нам такие суммы не подъемны." sms_unknown "{image=emoji sweat} Ух... Извини что побеспокоил. Нам такие суммы не подъемны."
$ sms_hide()
with dissolve
jump fast_end2 jump fast_end2
else: else:
unknown_char "Ок. Нас устраивает. Скину файл с заданием и доступы." sms_unknown "Ок. Нас устраивает. Скину файл с заданием и доступы."
$ sms_hide()
with dissolve
jump game1 jump game1
return return
@ -176,11 +174,33 @@ label game1:
return return
label game2: label game2:
mystic_char "Не смей запускать скрипт тестирования нейросети!" # Переписка с неизвестным от Палармо
root "Ты кто?" $ sms_mystic = SMSL(_("╨п╨╕╨╖╨▒╤Г╨┤╤Г╤Й╨╡╨│╨╛"))
mystic_char "Не смей запускать скрипт тестирования нейросети!"
root "Понятно. Лети в бан." # это слова получателя
$ sms_r = SMSR(_("Я"))
# сервисные сообщения
$ sms_c = SMSC(_("Системное сообщение"))
# имя абонента
$ sms_who = _("Неизвестный")
# цвет имени в шапке
$ sms_who_color = "#fc8619"
$ sms_show(_("╨п╨╕╨╖╨▒╤Г╨┤╤Г╤Й╨╡╨│╨╛"), True)
with dissolve
sms_mystic "Не смей запускать скрипт тестирования нейросети!"
sms_r "Ты кто?"
sms_mystic "Не смей запускать скрипт тестирования нейросети!"
sms_r "Понятно. Лети в бан."
$ sms_hide()
with dissolve
root_mind "Еще мне ботов не хватало. Интересно, по площадям бьют, или где-то в какой-то группе мессенджера охотник сидит? Ладно, не имеет значения." root_mind "Еще мне ботов не хватало. Интересно, по площадям бьют, или где-то в какой-то группе мессенджера охотник сидит? Ладно, не имеет значения."
jump game3 jump game3
return return
@ -216,31 +236,5 @@ label game3:
return return
label fast_end1:
play music "Anemoia - System #1.mp3" loop volume 0.5
nvl clear
root_mind "На удивление, я заснул в этот раз быстро и крепко."
sysmsg "System will be shutdown..."
sysmsg "...3"
sysmsg "...2"
sysmsg "...1"
sysmsg "Это была самая быстра концовка на Диком Западе!"
return
label fast_end2:
play music "Anemoia - System #1.mp3" loop volume 0.5
nvl clear
root_mind "Ну и не очень то и хотелось. Сами за копейки вкалывайте. Я лучше попробую спать лечь."
sysmsg "System will be shutdown..."
sysmsg "...3"
sysmsg "...2"
sysmsg "...1"
sysmsg "Суслик пошел домой и... никого не встретил!"
return

Binary file not shown.

293
game/sms.rpy Normal file
View File

@ -0,0 +1,293 @@
## модуль sms работает только в паре с модулем 7dots.rpy 2022 года
## для работы модуля нужно на экран "say" добавить строку:
## on "show" action SMSAdd(what)
# показать телефон на экране
# who - имя собеседника в шапке
# clear - очистить сообщения
# $ sms_show(who=None, clear=False)
# убрать телефон с экрана
# $ sms_hide()
# очистить экран телефона
# $ sms_clear()
# персонажи для переписки, собеседник
# $ sms_oksana = SMSL(_("Оксана Ш."))
# это слова получателя
# $ sms_r = SMSR(_("Я"))
# сервисные сообщения
# $ sms_c = SMSC(_("Системное сообщение"))
# имена лучше задавать, хоть их и не видно, для сохранения в истории
# чтобы переместить телефон, нужно поменять переменную:
# sms_trans = [align(.15, .5), rotate(-2.5, False)]
## НАСТРАИВАЕМЫЕ ПАРАМЕТРЫ
init python:
# размеры всего телефона
phone_w, phone_h = 500, 960
# размеры экрана телефона
scr_w, scr_h = 480, 860
# высота статичных участков (шапка и меню)
scr_top_h = 80
scr_bottom_h = 80
# цвет фона для шапки
scr_top_color = "#004472"
# фон экрана телефона
scr_bg = "#fff"
# максимальная ширина пузыря с сообщением
sms_w = 380
# если ширина/высота единственной в сообщении картинки больше этих,
# то картинка преобразуется в прикреплённое фото
sms_min_w, sms_min_h = 200, 200
# цвета фона
sms_left_bg_color = "#09f"
sms_right_bg_color = "#e5e5ea"
sms_center_bg_color = "#0000"
# цвета текста
sms_left_text_color = "#fff"
sms_right_text_color = "#000"
sms_center_text_color = "#aaa"
# шрифт
sms_text_size = 28
sms_text_font = "gui/fonts/Roboto-Regular.ttf"
# отступы текста sms от краёв пузырей
sms_xpadding, sms_ypadding = 24, 12
# отступы пузырей sms от краёв экрана
sms_xmargin, sms_ymargin = 24, 4
# фиксированные углы для пузырей с сообщениями (для Frame)
sms_corner_w, sms_corner_h = 16, 16
# трансформ для окна с телефоном
# например, можно задать положение
sms_trans = [align(.15, .5), rotate(-2.5, False)]
## остальное лучше не менять
# показать телефон на экране
def sms_show(who=None, clear=False):
if not sms_current_style:
store.sms_current_style = "sms_center"
if who is not None:
store.sms_who = who
# если нужно, очистить сообщения
if clear:
sms_clear()
renpy.show_screen("sms", _layer="master")
# убрать телефон с экрана
def sms_hide():
renpy.hide_screen("sms", layer="master")
# очистить экран телефона
def sms_clear():
store.sms_all = []
renpy.restart_interaction()
# текущее время читателя
def time_now():
return datetime.datetime.now().strftime("%H:%M")
init:
# стиль для фрейма с телефоном
style phone_window is empty:
xysize(phone_w, phone_h)
background None
foreground Frame("phone skin", 0, 0)
# стиль конкретно для экрана телефона
style scr_frame is empty:
xysize(scr_w, scr_h)
background Frame(scr_bg, 0, 0)
align(.5, .5)
# стиль для шапки с именем абонента
style top_frame is empty:
xysize(scr_w, scr_top_h)
background scr_top_color
foreground Frame("phone top", 0, 0)
align(.5, .0)
# стиль для подвала с псевдо-полем ввода
style bottom_frame is empty:
xysize(scr_w, scr_bottom_h)
background Frame("phone bottom", 0, 0)
align(.5, 1.)
# стиль для sms от собеседника
style sms_left is frame:
background Frame(At("phone mask", color(sms_left_bg_color)), sms_corner_w, sms_corner_h)
xmaximum sms_w
xpadding sms_xpadding
ypadding sms_ypadding
xmargin sms_xmargin
ymargin sms_ymargin
xalign .0
# стиль для sms от получателя
style sms_right is sms_left:
background Frame(At("phone mask", color(sms_right_bg_color)), sms_corner_w, sms_corner_h)
xalign 1.
# стиль для сервисных сообщений
style sms_center is sms_left:
background None
xmaximum scr_w
xalign .5
# стили для текста сообщений
style sms_left_text is text:
font sms_text_font
size sms_text_size
color sms_left_text_color
text_align .0
style sms_right_text is sms_left_text:
color sms_right_text_color
style sms_center_text is sms_left_text:
color sms_center_text_color
init -1 python:
# листать вниз при добавлении сообщений
yadjValue = float("inf")
yadj = ui.adjustment()
# все sms в формате кортежей (стиль, текст)
sms_all = []
# здесь будет храниться стиль текущего сообщения
sms_current_style = ""
from functools import partial
# определяем стиль текущего персонажа
def call_style(smsstyle, event_name, *args, **kwarg):
if event_name == "begin":
store.sms_current_style = smsstyle
# если это sms, то пикаем
if smsstyle in sms_beep_styles:
splay("new_message", ext="mp3")
# функция с параметрами в качестве одного параметра
def sms_style(*args, **kwarg):
return partial(call_style, *args, **kwarg)
# заготовки для невидимых на экране персонажей
def SMS(narrator=None, smsstyle="sms_center", *args, **kwarg):
return Character(narrator=narrator, window_style="empty", window_yoffset=config.screen_height, statement_name="say-centered", callback=sms_style(smsstyle), *args, **kwarg)
def SMSL(narrator=None, *args, **kwarg):
return SMS(narrator, "sms_left", *args, **kwarg)
def SMSR(narrator=None, *args, **kwarg):
return SMS(narrator, "sms_right", *args, **kwarg)
def SMSC(narrator=None, *args, **kwarg):
return SMS(narrator, *args, **kwarg)
sms_beep_styles = ["sms_left", "sms_right"]
# читаем сообщение, чтобы вывести на экран в общей куче
def sms_add(what=None):
# добавляем сообщение в список сообщений на экране
if what is not None and sms_current_style is not None:
store.sms_all.append((sms_current_style, what))
# если это не системное сообщение, то пикаем
if style in sms_beep_styles:
splay("new_message")
SMSAdd = renpy.curry(sms_add)
# не будет работать с пробелами, типа такого: "{ image = smile }"
# если вместо текста только картинка, то показать её с закруглёнными краями, а не пузырь
def sms_image(i_style, i_text):
if "{image=" in i_text.replace(" ", "") and not del_tags(i_text, ""):
imgs = get_tags(i_text, "image")
if imgs:
key = list(imgs.keys())
if len(key) == 1:
img = imgs[key[0]]
w, h = get_size(img)
# если картинка больше указанных в настройках размеров
if w >= sms_min_w or h >= sms_min_h:
# вписываем картинку в сообщение
if w > h:
z = sms_w / w
else:
z = sms_h / h
img = Transform(img, zoom=z)
w2, h2 = int(w * z), int(h * z)
# скругляем края
mask = Frame("phone mask", sms_corner_w, sms_corner_h, xysize=(w2, h2))
return AlphaMask(img, mask)
return None
# экран телефона
screen sms():
$ yadj.value = yadjValue
# окно с телефоном
frame:
style "phone_window"
at sms_trans
# фрейм с экраном телефона
frame:
style "scr_frame"
# стопочкой шапка, смски и подвал
vbox:
xfill True
yfill True
# шапка с абонентом
frame:
style "top_frame"
# имя абонента
text sms_who style "sms_center_text" align(.5, .7) color sms_who_color
# смски с прокруткой
viewport:
id "sms_vp"
# размеры окна прокрутки с учетом шапки и подвала
xysize(scr_w, scr_h - scr_top_h - scr_bottom_h)
xinitial 1.
yfill False
mousewheel True
draggable True
side_xfill True
transclude
# перематываем в конец
yadjustment yadj
# стопочкой все сообщения
vbox:
xfill True
for i_style, i_text in sms_all:
# если это единственная картинка больше пузыря
$ img = sms_image(i_style, i_text)
if img:
# вписываем её в пузырь
frame:
style i_style
xpadding 0
ypadding 0
background None
add img
# иначе просто выводим текст в пузыре
else:
textbutton i_text style i_style
# подвал с псевдо-полем ввода
frame:
style "bottom_frame"