diff --git a/game/.gitignore b/game/.gitignore index 47637e9..b5b7487 100644 --- a/game/.gitignore +++ b/game/.gitignore @@ -1,5 +1,5 @@ -game/cache -game/saves +./cache/ +./saves/ .vscode/ *.rpyc *.txt \ No newline at end of file diff --git a/game/.vscode/settings.json b/game/.vscode/settings.json index 46ebd25..bc188be 100644 --- a/game/.vscode/settings.json +++ b/game/.vscode/settings.json @@ -4,5 +4,126 @@ "**/*.rpa": true, "**/*.rpymc": 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" + } + } + ] } } \ No newline at end of file diff --git a/game/7dots.rpy b/game/7dots.rpy new file mode 100644 index 0000000..89b0739 --- /dev/null +++ b/game/7dots.rpy @@ -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)) diff --git a/game/audio/new_message.mp3 b/game/audio/new_message.mp3 new file mode 100644 index 0000000..36750e7 Binary files /dev/null and b/game/audio/new_message.mp3 differ diff --git a/game/audio/sms.ogg b/game/audio/sms.ogg new file mode 100644 index 0000000..fc5064f Binary files /dev/null and b/game/audio/sms.ogg differ diff --git a/game/ends.rpy b/game/ends.rpy new file mode 100644 index 0000000..1665367 --- /dev/null +++ b/game/ends.rpy @@ -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 \ No newline at end of file diff --git a/game/gui.rpy b/game/gui.rpy index e0f3f48..25a21f7 100644 --- a/game/gui.rpy +++ b/game/gui.rpy @@ -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 diff --git a/game/gui.rpyc b/game/gui.rpyc index 9e2c5e6..e0d1a2b 100644 Binary files a/game/gui.rpyc and b/game/gui.rpyc differ diff --git a/game/gui/fonts/Roboto-Black.ttf b/game/gui/fonts/Roboto-Black.ttf new file mode 100644 index 0000000..0112e7d Binary files /dev/null and b/game/gui/fonts/Roboto-Black.ttf differ diff --git a/game/gui/fonts/Roboto-BlackItalic.ttf b/game/gui/fonts/Roboto-BlackItalic.ttf new file mode 100644 index 0000000..b2c6aca Binary files /dev/null and b/game/gui/fonts/Roboto-BlackItalic.ttf differ diff --git a/game/gui/fonts/Roboto-Bold.ttf b/game/gui/fonts/Roboto-Bold.ttf new file mode 100644 index 0000000..43da14d Binary files /dev/null and b/game/gui/fonts/Roboto-Bold.ttf differ diff --git a/game/gui/fonts/Roboto-BoldItalic.ttf b/game/gui/fonts/Roboto-BoldItalic.ttf new file mode 100644 index 0000000..bcfdab4 Binary files /dev/null and b/game/gui/fonts/Roboto-BoldItalic.ttf differ diff --git a/game/gui/fonts/Roboto-Italic.ttf b/game/gui/fonts/Roboto-Italic.ttf new file mode 100644 index 0000000..1b5eaa3 Binary files /dev/null and b/game/gui/fonts/Roboto-Italic.ttf differ diff --git a/game/gui/fonts/Roboto-Light.ttf b/game/gui/fonts/Roboto-Light.ttf new file mode 100644 index 0000000..e7307e7 Binary files /dev/null and b/game/gui/fonts/Roboto-Light.ttf differ diff --git a/game/gui/fonts/Roboto-LightItalic.ttf b/game/gui/fonts/Roboto-LightItalic.ttf new file mode 100644 index 0000000..2d277af Binary files /dev/null and b/game/gui/fonts/Roboto-LightItalic.ttf differ diff --git a/game/gui/fonts/Roboto-Medium.ttf b/game/gui/fonts/Roboto-Medium.ttf new file mode 100644 index 0000000..ac0f908 Binary files /dev/null and b/game/gui/fonts/Roboto-Medium.ttf differ diff --git a/game/gui/fonts/Roboto-MediumItalic.ttf b/game/gui/fonts/Roboto-MediumItalic.ttf new file mode 100644 index 0000000..fc36a47 Binary files /dev/null and b/game/gui/fonts/Roboto-MediumItalic.ttf differ diff --git a/game/gui/fonts/Roboto-Regular.ttf b/game/gui/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/game/gui/fonts/Roboto-Regular.ttf differ diff --git a/game/gui/fonts/Roboto-Thin.ttf b/game/gui/fonts/Roboto-Thin.ttf new file mode 100644 index 0000000..2e0dee6 Binary files /dev/null and b/game/gui/fonts/Roboto-Thin.ttf differ diff --git a/game/gui/fonts/Roboto-ThinItalic.ttf b/game/gui/fonts/Roboto-ThinItalic.ttf new file mode 100644 index 0000000..084f9c0 Binary files /dev/null and b/game/gui/fonts/Roboto-ThinItalic.ttf differ diff --git a/game/terminus.ttf b/game/gui/fonts/terminus.ttf similarity index 100% rename from game/terminus.ttf rename to game/gui/fonts/terminus.ttf diff --git a/game/images/emoji/rofl.png b/game/images/emoji/rofl.png new file mode 100644 index 0000000..9029505 Binary files /dev/null and b/game/images/emoji/rofl.png differ diff --git a/game/images/emoji/sweat.png b/game/images/emoji/sweat.png new file mode 100644 index 0000000..393c4ce Binary files /dev/null and b/game/images/emoji/sweat.png differ diff --git a/game/images/phone bottom.png b/game/images/phone bottom.png new file mode 100644 index 0000000..5b54620 Binary files /dev/null and b/game/images/phone bottom.png differ diff --git a/game/images/phone mask.png b/game/images/phone mask.png new file mode 100644 index 0000000..9e19d25 Binary files /dev/null and b/game/images/phone mask.png differ diff --git a/game/images/phone skin.png b/game/images/phone skin.png new file mode 100644 index 0000000..e7cc5d9 Binary files /dev/null and b/game/images/phone skin.png differ diff --git a/game/images/phone top.png b/game/images/phone top.png new file mode 100644 index 0000000..2280516 Binary files /dev/null and b/game/images/phone top.png differ diff --git a/game/images/phone/bottom.png b/game/images/phone/bottom.png new file mode 100644 index 0000000..5b54620 Binary files /dev/null and b/game/images/phone/bottom.png differ diff --git a/game/images/phone/mask.png b/game/images/phone/mask.png new file mode 100644 index 0000000..9e19d25 Binary files /dev/null and b/game/images/phone/mask.png differ diff --git a/game/images/phone/skin.png b/game/images/phone/skin.png new file mode 100644 index 0000000..e7cc5d9 Binary files /dev/null and b/game/images/phone/skin.png differ diff --git a/game/images/phone/top.png b/game/images/phone/top.png new file mode 100644 index 0000000..2280516 Binary files /dev/null and b/game/images/phone/top.png differ diff --git a/game/init.rpy b/game/init.rpy new file mode 100644 index 0000000..8eba366 --- /dev/null +++ b/game/init.rpy @@ -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 \ No newline at end of file diff --git a/game/screens.rpy b/game/screens.rpy index ca5c029..429c6e0 100644 --- a/game/screens.rpy +++ b/game/screens.rpy @@ -95,6 +95,8 @@ style frame: ## https://www.renpy.org/doc/html/screen_special.html#say screen say(who, what): + on "show" action SMSAdd(what) + style_prefix "say" window: diff --git a/game/screens.rpyc b/game/screens.rpyc index 75c109f..6b7accf 100644 Binary files a/game/screens.rpyc and b/game/screens.rpyc differ diff --git a/game/script.rpy b/game/script.rpy index 17fcb56..8b5660d 100644 --- a/game/script.rpy +++ b/game/script.rpy @@ -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: play music "UNIVERSFIELD - Orion Nebula 2.mp3" loop volume 0.5 scene console @@ -33,22 +18,7 @@ label start: scene root_room 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 """ Рабочий вечер после трудного рабочего дня. Что может быть лучше? Только рабочий вечер с кружкой горячего кофе. @@ -116,20 +86,48 @@ label start: label game: 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: rubperhour = renpy.input("Час вашей работы стоит:", length=32) rubperhour = int(rubperhour.strip()) if not rubperhour: rubperhour = 3000 - + sms_r "[rubperhour] рублей за час. Оплата минимум за час, даже если делов на 5 минут." + if rubperhour > 5000: - unknown_char "Ух... Извини что побеспокоил. Нам такие суммы не подъемны." + sms_unknown "{image=emoji sweat} Ух... Извини что побеспокоил. Нам такие суммы не подъемны." + $ sms_hide() + with dissolve jump fast_end2 else: - unknown_char "Ок. Нас устраивает. Скину файл с заданием и доступы." + sms_unknown "Ок. Нас устраивает. Скину файл с заданием и доступы." + $ sms_hide() + with dissolve jump game1 - + return label game1: @@ -176,11 +174,33 @@ label game1: return label game2: - mystic_char "Не смей запускать скрипт тестирования нейросети!" - root "Ты кто?" - mystic_char "Не смей запускать скрипт тестирования нейросети!" - root "Понятно. Лети в бан." + # Переписка с неизвестным от Палармо + $ sms_mystic = SMSL(_("╨п╨╕╨╖╨▒╤Г╨┤╤Г╤Й╨╡╨│╨╛")) + + # это слова получателя + $ 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 "Еще мне ботов не хватало. Интересно, по площадям бьют, или где-то в какой-то группе мессенджера охотник сидит? Ладно, не имеет значения." + jump game3 return @@ -216,31 +236,5 @@ label game3: 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 diff --git a/game/script.rpyc b/game/script.rpyc index 3442985..54bcabc 100644 Binary files a/game/script.rpyc and b/game/script.rpyc differ diff --git a/game/sms.rpy b/game/sms.rpy new file mode 100644 index 0000000..ca0b68f --- /dev/null +++ b/game/sms.rpy @@ -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"