game_launcher 0.1.0

Shared launcher layer for the app builds: engine detection, titles, covers and a common runtime for SiglusEngine, RealLive, AVG32 and UK2
use std::path::Path;

use anyhow::Result;
use reallive::engine::{Engine, EngineOptions};
use reallive::input::{Button, InputEvent, Key};

use super::{FramebufferGame, GameKey, PointerButton};
use crate::nls::Nls;

pub struct RealLiveGame {
    engine: Engine,
    frame: Vec<u8>,
    size: (u32, u32),
    ended: bool,
}

impl RealLiveGame {
    pub fn open(root: &Path, nls: Nls) -> Result<Self> {
        let mut options = EngineOptions::new(root);
        options.nls = nls.to_reallive();
        let engine = Engine::open(options)?;
        let size = (
            engine.machine.sys.gfx.width.max(1) as u32,
            engine.machine.sys.gfx.height.max(1) as u32,
        );
        Ok(Self {
            engine,
            frame: vec![0; (size.0 * size.1 * 4) as usize],
            size,
            ended: false,
        })
    }
}

fn map_key(key: GameKey) -> Key {
    match key {
        GameKey::Enter => Key::Enter,
        GameKey::Escape => Key::Escape,
        GameKey::Space => Key::Space,
        GameKey::Up => Key::Up,
        GameKey::Down => Key::Down,
        GameKey::Left => Key::Left,
        GameKey::Right => Key::Right,
        GameKey::PageUp => Key::PageUp,
        GameKey::PageDown => Key::PageDown,
        GameKey::Home => Key::Home,
        GameKey::End => Key::End,
        GameKey::Backspace => Key::Backspace,
        GameKey::Tab => Key::Tab,
        GameKey::Ctrl => Key::Ctrl,
        GameKey::Shift => Key::Shift,
        GameKey::F(n) => Key::F(n),
        GameKey::Char(c) => Key::Char(c),
    }
}

impl FramebufferGame for RealLiveGame {
    fn size(&self) -> (u32, u32) {
        self.size
    }

    fn step(&mut self, dt_ms: u32) -> bool {
        if self.ended {
            return false;
        }
        self.engine.tick(u64::from(dt_ms));
        for error in self.engine.machine.diagnostics.errors.drain(..) {
            log::warn!("reallive: {error}");
        }
        if self.engine.finished() {
            self.shutdown();
            return false;
        }
        let surface = self.engine.render();
        self.size = (surface.width.max(1) as u32, surface.height.max(1) as u32);
        self.frame = surface.rgba;
        true
    }

    fn frame(&self) -> &[u8] {
        &self.frame
    }

    fn pointer_move(&mut self, x: i32, y: i32) {
        self.engine.machine.sys.input.mouse = (x, y);
    }

    fn pointer_button(&mut self, button: PointerButton, pressed: bool) {
        let button = match button {
            PointerButton::Left => Button::Left,
            PointerButton::Right => Button::Right,
        };
        self.engine.input(if pressed {
            InputEvent::Press(button)
        } else {
            InputEvent::Release(button)
        });
    }

    fn wheel(&mut self, up: bool) {
        self.engine.input(InputEvent::Press(if up {
            Button::WheelUp
        } else {
            Button::WheelDown
        }));
    }

    fn key(&mut self, key: GameKey, pressed: bool) {
        let key = map_key(key);
        self.engine.input(if pressed {
            InputEvent::KeyDown(key)
        } else {
            InputEvent::KeyUp(key)
        });
    }

    fn text(&mut self, text: &str) {
        self.engine.input(InputEvent::Text(text.to_owned()));
    }

    fn cursor_visible(&self) -> bool {
        self.engine.machine.sys.cursor_visible
    }

    fn title(&self) -> String {
        let sys = &self.engine.machine.sys;
        if sys.title.is_empty() {
            sys.gameexe.str("CAPTION").unwrap_or("RealLive").to_owned()
        } else {
            sys.title.clone()
        }
    }

    fn shutdown(&mut self) {
        if !self.ended {
            reallive::save::save_global(&self.engine.machine).ok();
        }
        self.ended = true;
    }
}