/* * pop_timer.c — единый владелец глобального таймера (FG4, банк 9). * * SDLPoP хранит rem_min/rem_tick и уменьшает rem_tick на каждом логическом * игровом кадре. Здесь та же схема: после нуля tick возвращается к 719, * поэтому 720 тиков образуют минуту. Настройки, экранные сообщения и * переход TIME_EXPIRED остаются снаружи: это делает ядро таймера чистым и * не привязывает его к ещё не реализованным title/cutscene режимам. */ #include #include "pop_timer.h" uint8_t pop_timer_minutes; uint16_t pop_timer_ticks; uint8_t pop_show_time; /* Накопленные кадры луча, ещё не сложившиеся в тик. Живёт между кадрами: * при FAST логический кадр короче эталона, и остаток переносится вперёд. */ static uint8_t tick_acc; void pop_timer_new_game(void) __banked { pop_timer_minutes = POP_TIMER_START_MINUTES; pop_timer_ticks = POP_TIMER_START_TICKS; pop_show_time = 0; tick_acc = 0; } uint8_t pop_timer_tick(uint8_t enabled, uint8_t may_run, uint8_t spent, uint8_t base) __banked { if (!enabled || !may_run || pop_timer_minutes == 0) return 0; /* ХОД ЧАСОВ ОТВЯЗАН ОТ ТЕМПА. Тик стоит `base` кадров луча — столько, * сколько их в кадре режима NORMAL. При NORMAL spent == base, и тик * приходится ровно на кадр, как было всегда; в быстрых режимах кадр * короче, остаток копится, и за то же РЕАЛЬНОЕ время выходит столько * же тиков. Цикла не нужно: spent никогда не больше base (быстрые * режимы кадр только УКОРАЧИВАЮТ) — значит не больше тика за вызов. */ /* Пауза, меню и загрузка сюда не заходят вовсе, поэтому за время их * работы кадры луча накапливаются мимо нас. Ограничиваем вклад одного * вызова: иначе после меню часы прыгнули бы вперёд на всю паузу. */ if (spent > (uint8_t)(base + base)) spent = base; tick_acc = (uint8_t)(tick_acc + spent); if (tick_acc < base) return 0; tick_acc = (uint8_t)(tick_acc - base); --pop_timer_ticks; if (pop_timer_ticks != 0) { /* Последняя минута: оригинал напоминает о времени КАЖДУЮ секунду * (rem_tick % 12 == 0) и специально гасит текущую строку, чтобы * новая встала немедленно, не дожидаясь её 24 тиков. */ if (pop_timer_minutes == 1 && pop_timer_ticks % 12 == 0) pop_show_time = 2; /* 2 = «показать, перебив текущую» */ return 0; } pop_timer_ticks = POP_TIMER_START_TICKS; --pop_timer_minutes; /* Круглые пятёрки и весь финальный отсчёт: seg008:1795. */ if (pop_timer_minutes != 0 && (pop_timer_minutes <= 5 || pop_timer_minutes % 5 == 0)) pop_show_time = 1; return (uint8_t)(pop_timer_minutes == 0); } void pop_timer_cheat_subtract(void) __banked { /* Это буквально правило SDLK_KP_MINUS: последнюю минуту читом не * отнимаем, иначе не получится отличить чит от настоящего expiry. */ if (pop_timer_minutes > 1) --pop_timer_minutes; pop_show_time = 2; /* оригинал показывает результат сразу */ } void pop_timer_cheat_add(void) __banked { /* SDLPoP хранит signed short, у нас достаточно byte: 255 минут — * практический максимум и насыщение не даёт тихо свернуться в нуль. */ if (pop_timer_minutes != 0xFF) ++pop_timer_minutes; pop_show_time = 2; } void pop_timer_qsave(pop_qs_io_t *io) __banked { uint8_t minutes = pop_timer_minutes; uint16_t ticks = pop_timer_ticks; pop_qs_u8(io, &minutes); pop_qs_u16(io, &ticks); if (io->reading && io->apply) { if (ticks == 0 || ticks > POP_TIMER_START_TICKS) { io->failed = 1; return; } pop_timer_minutes = minutes; pop_timer_ticks = ticks; } }