1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-07-09 22:16:20 +00:00

Refactor input system and other stuff

This commit is contained in:
Alula 2020-11-28 20:25:51 +01:00
parent 65bc0b6b88
commit 605a858c90
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
28 changed files with 1106 additions and 469 deletions

View file

@ -77,7 +77,7 @@ strum_macros = "0.18.0"
# remove and replace when drain_filter is in stable
vec_mut_scan = "0.3.0"
webbrowser = "0.5.5"
winit = "0.23.0"
winit = {version = "0.23.0", features = ["serde"]}
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.2.0"

View file

@ -5,7 +5,7 @@ use std::io::ErrorKind;
use std::io::SeekFrom;
use std::path::{Component, Path, PathBuf};
use ggez::{GameError, GameResult};
use ggez::GameResult;
use ggez::GameError::FilesystemError;
use ggez::vfs::{OpenOptions, VFile, VFS, VMetadata};

View file

@ -1,7 +1,6 @@
use std::cmp::Ordering;
use num_traits::{AsPrimitive, Num};
use num_traits::real::Real;
use serde::{Deserialize, Serialize};
use crate::bitfield;
@ -82,22 +81,6 @@ bitfield! {
pub drs_destroyed, set_drs_destroyed: 15;
}
bitfield! {
#[derive(Clone, Copy)]
pub struct KeyState(u16);
impl Debug;
pub left, set_left: 0;
pub right, set_right: 1;
pub up, set_up: 2;
pub down, set_down: 3;
pub map, set_map: 4;
pub jump, set_jump: 5;
pub fire, set_fire: 6;
pub weapon_next, set_weapon_next: 7;
pub weapon_prev, set_weapon_prev: 8;
}
bitfield! {
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct ControlFlags(u16);

View file

@ -52,7 +52,7 @@ impl BossLifeBar {
}
impl GameEntity<&NPCMap> for BossLifeBar {
fn tick(&mut self, state: &mut SharedGameState, npc_map: &NPCMap) -> GameResult<()> {
fn tick(&mut self, _state: &mut SharedGameState, npc_map: &NPCMap) -> GameResult<()> {
match self.target {
BossLifeTarget::NPC(npc_id) => {
if let Some(npc_cell) = npc_map.npcs.get(&npc_id) {

View file

@ -1 +1,2 @@
pub mod boss_life_bar;
pub mod stage_select;

View file

@ -0,0 +1,123 @@
use ggez::{Context, GameResult};
use crate::common::Rect;
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::player::Player;
use crate::shared_game_state::SharedGameState;
use crate::text_script::ScriptMode;
pub struct StageSelect {
pub current_teleport_slot: u8,
stage_select_text_y_pos: usize,
tick: usize,
}
impl StageSelect {
pub fn new() -> StageSelect {
StageSelect {
current_teleport_slot: 0,
stage_select_text_y_pos: 54,
tick: 0,
}
}
pub fn reset(&mut self) {
self.stage_select_text_y_pos = 54;
self.current_teleport_slot = 0;
self.tick = 0;
}
}
impl GameEntity<(&Player, &Player)> for StageSelect {
fn tick(&mut self, state: &mut SharedGameState, (player1, player2): (&Player, &Player)) -> GameResult {
let slot_count = state.teleporter_slots.iter()
.filter(|&&(index, _event_num)| index != 0)
.count();
if self.stage_select_text_y_pos > 46 {
self.stage_select_text_y_pos -= 1;
}
let left_pressed = player1.controller.trigger_left() || player2.controller.trigger_left();
let right_pressed = player1.controller.trigger_right() || player2.controller.trigger_right();
let ok_pressed = player1.controller.trigger_jump() || player1.controller.trigger_menu_ok()
|| player2.controller.trigger_jump() || player2.controller.trigger_menu_ok();
let cancel_pressed = player1.controller.trigger_shoot() || player2.controller.trigger_shoot();
if left_pressed {
if self.current_teleport_slot == 0 {
self.current_teleport_slot = slot_count.saturating_sub(1) as u8;
} else {
self.current_teleport_slot -= 1;
}
} else if right_pressed {
if self.current_teleport_slot == slot_count.saturating_sub(1) as u8 {
self.current_teleport_slot = 0;
} else {
self.current_teleport_slot += 1;
}
}
if left_pressed || right_pressed {
state.sound_manager.play_sfx(1);
if let Some(&(index, _event_num)) = state.teleporter_slots.get(self.current_teleport_slot as usize) {
state.textscript_vm.start_script(1000 + index);
} else {
state.textscript_vm.start_script(1000);
}
}
if ok_pressed || cancel_pressed {
self.reset();
state.textscript_vm.set_mode(ScriptMode::Map);
state.control_flags.set_tick_world(true);
state.control_flags.set_control_enabled(true);
state.control_flags.set_interactions_disabled(false);
if ok_pressed {
if let Some(&(_index, event_num)) = state.teleporter_slots.get(self.current_teleport_slot as usize) {
state.textscript_vm.start_script(event_num);
}
}
}
self.tick += 1;
Ok(())
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "StageImage")?;
let slot_count = state.teleporter_slots.iter()
.filter(|&&(index, _event_num)| index != 0)
.count();
let slot_offset = ((state.canvas_size.0 - 40.0 * slot_count as f32) / 2.0).floor();
let mut slot_rect = Rect::new(0, 0, 0, 0);
for i in 0..slot_count {
let index = state.teleporter_slots[i].0;
slot_rect.left = 32 * (index as u16 % 8);
slot_rect.top = 16 * (index as u16 / 8);
slot_rect.right = slot_rect.left + 32;
slot_rect.bottom = slot_rect.top + 16;
batch.add_rect(slot_offset + i as f32 * 40.0, 64.0, &slot_rect);
}
batch.draw(ctx)?;
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
batch.add_rect(128.0, self.stage_select_text_y_pos as f32, &state.constants.textscript.stage_select_text);
if slot_count > 0 {
batch.add_rect(slot_offset + self.current_teleport_slot as f32 * 40.0, 64.0, &state.constants.textscript.cursor[self.tick / 2 % 2]);
}
batch.draw(ctx)?;
Ok(())
}
}

View file

@ -0,0 +1,73 @@
use crate::input::player_controller::PlayerController;
use crate::shared_game_state::SharedGameState;
use ggez::{GameResult, Context};
pub struct CombinedMenuController {
controllers: Vec<Box<dyn PlayerController>>,
}
impl CombinedMenuController {
pub fn new() -> CombinedMenuController {
CombinedMenuController {
controllers: Vec::new(),
}
}
pub fn update(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
for cont in self.controllers.iter_mut() {
cont.update(state, ctx)?;
}
Ok(())
}
pub fn update_trigger(&mut self) {
for cont in self.controllers.iter_mut() {
cont.update_trigger();
}
}
pub fn add(&mut self, controller: Box<dyn PlayerController>) {
self.controllers.push(controller);
}
pub fn trigger_up(&self) -> bool {
for cont in self.controllers.iter() {
if cont.trigger_up() {
return true;
}
}
false
}
pub fn trigger_down(&self) -> bool {
for cont in self.controllers.iter() {
if cont.trigger_down() {
return true;
}
}
false
}
pub fn trigger_ok(&self) -> bool {
for cont in self.controllers.iter() {
if cont.trigger_menu_ok() {
return true;
}
}
false
}
pub fn trigger_back(&self) -> bool {
for cont in self.controllers.iter() {
if cont.trigger_menu_back() {
return true;
}
}
false
}
}

View file

@ -0,0 +1,122 @@
use ggez::{Context, GameResult};
use crate::input::player_controller::PlayerController;
use crate::shared_game_state::SharedGameState;
/// A no-op implementation of player controller.
#[derive(Clone)]
pub struct DummyPlayerController;
impl DummyPlayerController {
pub fn new() -> DummyPlayerController {
DummyPlayerController
}
}
impl PlayerController for DummyPlayerController {
fn update(&mut self, _state: &SharedGameState, _ctx: &mut Context) -> GameResult {
Ok(())
}
fn update_trigger(&mut self) {}
fn move_up(&self) -> bool {
false
}
fn move_left(&self) -> bool {
false
}
fn move_down(&self) -> bool {
false
}
fn move_right(&self) -> bool {
false
}
fn prev_weapon(&self) -> bool {
false
}
fn next_weapon(&self) -> bool {
false
}
fn jump(&self) -> bool {
false
}
fn shoot(&self) -> bool {
false
}
fn trigger_up(&self) -> bool {
false
}
fn trigger_left(&self) -> bool {
false
}
fn trigger_down(&self) -> bool {
false
}
fn trigger_right(&self) -> bool {
false
}
fn trigger_prev_weapon(&self) -> bool {
false
}
fn trigger_next_weapon(&self) -> bool {
false
}
fn trigger_jump(&self) -> bool {
false
}
fn trigger_shoot(&self) -> bool {
false
}
fn trigger_menu_ok(&self) -> bool {
false
}
fn trigger_menu_back(&self) -> bool {
false
}
fn trigger_menu_pause(&self) -> bool {
false
}
fn look_up(&self) -> bool {
false
}
fn look_left(&self) -> bool {
false
}
fn look_down(&self) -> bool {
false
}
fn look_right(&self) -> bool {
false
}
fn move_analog_x(&self) -> f64 {
0.0
}
fn move_analog_y(&self) -> f64 {
0.0
}
}

View file

@ -0,0 +1,193 @@
use ggez::{Context, GameResult};
use ggez::input::keyboard;
use winit::event::VirtualKeyCode;
use crate::bitfield;
use crate::input::player_controller::PlayerController;
use crate::player::TargetPlayer;
use crate::shared_game_state::SharedGameState;
bitfield! {
#[derive(Clone, Copy)]
pub struct KeyState(u16);
impl Debug;
pub left, set_left: 0;
pub right, set_right: 1;
pub up, set_up: 2;
pub down, set_down: 3;
pub map, set_map: 4;
pub inventory, set_inventory: 5;
pub jump, set_jump: 6;
pub shoot, set_shoot: 7;
pub next_weapon, set_next_weapon: 8;
pub prev_weapon, set_prev_weapon: 9;
pub escape, set_escape: 10;
pub enter, set_enter: 10;
}
#[derive(Clone)]
pub struct KeyboardController {
target: TargetPlayer,
state: KeyState,
old_state: KeyState,
trigger: KeyState,
}
impl KeyboardController {
pub fn new(target: TargetPlayer) -> KeyboardController {
KeyboardController {
target,
state: KeyState(0),
old_state: KeyState(0),
trigger: KeyState(0),
}
}
}
impl PlayerController for KeyboardController {
fn update(&mut self, state: &SharedGameState, ctx: &mut Context) -> GameResult {
let keymap = match self.target {
TargetPlayer::Player1 => &state.settings.player1_key_map,
TargetPlayer::Player2 => &state.settings.player2_key_map,
};
self.state.set_left(keyboard::is_key_pressed(ctx, keymap.left));
self.state.set_up(keyboard::is_key_pressed(ctx, keymap.up));
self.state.set_right(keyboard::is_key_pressed(ctx, keymap.right));
self.state.set_down(keyboard::is_key_pressed(ctx, keymap.down));
self.state.set_map(keyboard::is_key_pressed(ctx, keymap.map));
self.state.set_inventory(keyboard::is_key_pressed(ctx, keymap.inventory));
self.state.set_jump(keyboard::is_key_pressed(ctx, keymap.jump));
self.state.set_shoot(keyboard::is_key_pressed(ctx, keymap.shoot));
self.state.set_prev_weapon(keyboard::is_key_pressed(ctx, keymap.prev_weapon));
self.state.set_next_weapon(keyboard::is_key_pressed(ctx, keymap.next_weapon));
self.state.set_enter(keyboard::is_key_pressed(ctx, VirtualKeyCode::Return));
self.state.set_escape(keyboard::is_key_pressed(ctx, VirtualKeyCode::Escape));
Ok(())
}
fn update_trigger(&mut self) {
let mut trigger = self.state.0 ^ self.old_state.0;
trigger &= self.state.0;
self.old_state = self.state;
self.trigger = KeyState(trigger);
}
fn move_up(&self) -> bool {
self.state.up()
}
fn move_left(&self) -> bool {
self.state.left()
}
fn move_down(&self) -> bool {
self.state.down()
}
fn move_right(&self) -> bool {
self.state.right()
}
fn prev_weapon(&self) -> bool {
self.state.prev_weapon()
}
fn next_weapon(&self) -> bool {
self.state.next_weapon()
}
fn jump(&self) -> bool {
self.state.jump()
}
fn shoot(&self) -> bool {
self.state.shoot()
}
fn trigger_up(&self) -> bool {
self.trigger.up()
}
fn trigger_left(&self) -> bool {
self.trigger.left()
}
fn trigger_down(&self) -> bool {
self.trigger.down()
}
fn trigger_right(&self) -> bool {
self.trigger.right()
}
fn trigger_prev_weapon(&self) -> bool {
self.trigger.prev_weapon()
}
fn trigger_next_weapon(&self) -> bool {
self.trigger.next_weapon()
}
fn trigger_jump(&self) -> bool {
self.trigger.jump()
}
fn trigger_shoot(&self) -> bool {
self.trigger.shoot()
}
fn trigger_menu_ok(&self) -> bool {
self.trigger.jump() || self.trigger.enter()
}
fn trigger_menu_back(&self) -> bool {
self.trigger.shoot() || self.trigger.escape()
}
fn trigger_menu_pause(&self) -> bool {
self.trigger.escape()
}
fn look_up(&self) -> bool {
self.state.up()
}
fn look_left(&self) -> bool {
self.state.left()
}
fn look_down(&self) -> bool {
self.state.down()
}
fn look_right(&self) -> bool {
self.state.right()
}
fn move_analog_x(&self) -> f64 {
if self.state.left() && self.state.right() {
0.0
} else if self.state.left() {
-1.0
} else if self.state.right() {
1.0
} else {
0.0
}
}
fn move_analog_y(&self) -> f64 {
if self.state.up() && self.state.down() {
0.0
} else if self.state.up() {
-1.0
} else if self.state.down() {
1.0
} else {
0.0
}
}
}

5
src/input/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod combined_menu_controller;
pub mod dummy_player_controller;
pub mod keyboard_player_controller;
pub mod player_controller;
pub mod touch_controls;

View file

@ -0,0 +1,95 @@
use ggez::{Context, GameResult};
use crate::shared_game_state::SharedGameState;
pub trait PlayerController: PlayerControllerClone {
fn update(&mut self, state: &SharedGameState, ctx: &mut Context) -> GameResult;
fn update_trigger(&mut self);
/// True if "move up" button is down.
fn move_up(&self) -> bool;
/// True if "move left" button is down.
fn move_left(&self) -> bool;
/// True if "move down" button is down.
fn move_down(&self) -> bool;
/// True if "move right" button is down.
fn move_right(&self) -> bool;
/// True if "prev weapon" button is down.
fn prev_weapon(&self) -> bool;
/// True if "next weapon" button is down.
fn next_weapon(&self) -> bool;
/// True if "jump" button is down.
fn jump(&self) -> bool;
/// True if "shoot" button is down.
fn shoot(&self) -> bool;
fn trigger_up(&self) -> bool;
fn trigger_left(&self) -> bool;
fn trigger_down(&self) -> bool;
fn trigger_right(&self) -> bool;
fn trigger_prev_weapon(&self) -> bool;
fn trigger_next_weapon(&self) -> bool;
fn trigger_jump(&self) -> bool;
fn trigger_shoot(&self) -> bool;
fn trigger_menu_ok(&self) -> bool;
fn trigger_menu_back(&self) -> bool;
fn trigger_menu_pause(&self) -> bool;
/// Optional, useful for controllers with two analog sticks.
/// Returns true if player looks towards upper direction.
fn look_up(&self) -> bool;
/// Optional, useful for controllers with two analog sticks.
/// Returns true if player looks towards left direction.
fn look_left(&self) -> bool;
/// Optional, useful for controllers with two analog sticks.
/// Returns true if player looks towards bottom direction.
fn look_down(&self) -> bool;
/// Optional, useful for controllers with two analog sticks.
/// Returns true if player looks towards right direction.
fn look_right(&self) -> bool;
/// Returns movement analog stick state in X axis within (-1.0..=1.0) range
/// In case of non-analog controllers this should return -1.0, 0.0 or 1.0, depending on keys pressed.
fn move_analog_x(&self) -> f64;
/// Returns movement analog stick state in Y axis within (-1.0..=1.0) range
/// In case of non-analog controllers this should return -1.0, 0.0 or 1.0, depending on keys pressed.
fn move_analog_y(&self) -> f64;
}
pub trait PlayerControllerClone {
fn clone_box(&self) -> Box<dyn PlayerController>;
}
impl<T: 'static + PlayerController + Clone> PlayerControllerClone for T {
fn clone_box(&self) -> Box<dyn PlayerController> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn PlayerController> {
fn clone(&self) -> Box<dyn PlayerController> {
self.clone_box()
}
}

View file

@ -1,8 +1,9 @@
use winit::event::TouchPhase;
use crate::texture_set::TextureSet;
use ggez::{Context, GameResult};
use crate::common::{KeyState, Rect};
use winit::event::TouchPhase;
use crate::common::Rect;
use crate::engine_constants::EngineConstants;
use crate::texture_set::TextureSet;
struct TouchPoint {
id: u64,
@ -20,8 +21,8 @@ impl TouchControls {
points: Vec::with_capacity(8),
}
}
pub fn process_winit_event(&mut self, scale: f32, key_state: &mut KeyState, touch: winit::event::Touch) {
pub fn process_winit_event(&mut self, scale: f32, touch: winit::event::Touch) {
match touch.phase {
TouchPhase::Started | TouchPhase::Moved => {
if let Some(point) = self.points.iter_mut().find(|p| p.id == touch.id) {
@ -31,7 +32,7 @@ impl TouchControls {
self.points.push(TouchPoint {
id: touch.id,
position: (touch.location.x, touch.location.y),
last_position: (0.0, 0.0)
last_position: (0.0, 0.0),
});
}
}

View file

@ -8,7 +8,7 @@ use std::{env, mem};
use std::path;
use std::time::Instant;
use ggez::{Context, ContextBuilder, filesystem, GameError, GameResult};
use ggez::{Context, ContextBuilder, GameError, GameResult};
use ggez::conf::{Backend, WindowMode, WindowSetup};
use ggez::event::{KeyCode, KeyMods};
use ggez::filesystem::mount_vfs;
@ -20,7 +20,7 @@ use ggez::mint::ColumnMatrix4;
use ggez::nalgebra::Vector2;
use log::*;
use pretty_env_logger::env_logger::Env;
use winit::event::{ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
use winit::event::{ElementState, Event, KeyboardInput, WindowEvent};
use winit::event_loop::ControlFlow;
use crate::builtin_fs::BuiltinFS;
@ -42,6 +42,7 @@ mod engine_constants;
mod entity;
mod frame;
mod inventory;
mod input;
mod live_debugger;
mod macros;
mod map;
@ -49,16 +50,16 @@ mod menu;
mod npc;
mod physics;
mod player;
mod player_hit;
mod profile;
mod rng;
mod scene;
mod settings;
mod shaders;
mod shared_game_state;
mod stage;
mod sound;
mod text_script;
mod texture_set;
mod touch_controls;
mod ui;
mod weapon;
@ -165,14 +166,6 @@ impl Game {
// todo: proper keymaps?
let state = &mut self.state;
match key_code {
KeyCode::Left => { state.key_state.set_left(true) }
KeyCode::Right => { state.key_state.set_right(true) }
KeyCode::Up => { state.key_state.set_up(true) }
KeyCode::Down => { state.key_state.set_down(true) }
KeyCode::Z => { state.key_state.set_jump(true) }
KeyCode::X => { state.key_state.set_fire(true) }
KeyCode::A => { state.key_state.set_weapon_prev(true) }
KeyCode::S => { state.key_state.set_weapon_next(true) }
KeyCode::F7 => { state.set_speed(1.0) }
KeyCode::F8 => {
if state.settings.speed > 0.2 {
@ -191,21 +184,8 @@ impl Game {
}
}
fn key_up_event(&mut self, key_code: KeyCode, _key_mod: KeyMods) {
let state = &mut self.state;
match key_code {
KeyCode::Left => { state.key_state.set_left(false) }
KeyCode::Right => { state.key_state.set_right(false) }
KeyCode::Up => { state.key_state.set_up(false) }
KeyCode::Down => { state.key_state.set_down(false) }
KeyCode::Z => { state.key_state.set_jump(false) }
KeyCode::X => { state.key_state.set_fire(false) }
KeyCode::A => { state.key_state.set_weapon_prev(false) }
KeyCode::S => { state.key_state.set_weapon_next(false) }
_ => {}
}
//
}
}
@ -385,7 +365,7 @@ pub fn init() -> GameResult {
}
WindowEvent::Touch(touch) => {
if let Some(game) = &mut game {
game.state.touch_controls.process_winit_event(game.state.scale, &mut game.state.key_state, touch);
game.state.touch_controls.process_winit_event(game.state.scale, touch);
}
}
WindowEvent::KeyboardInput {

View file

@ -64,10 +64,10 @@ impl LiveDebugger {
.build(ui, || {
ui.text(format!(
"Player position: ({:.1},{:.1}), velocity: ({:.1},{:.1})",
game_scene.player.x as f32 / 512.0,
game_scene.player.y as f32 / 512.0,
game_scene.player.vel_x as f32 / 512.0,
game_scene.player.vel_y as f32 / 512.0,
game_scene.player1.x as f32 / 512.0,
game_scene.player1.y as f32 / 512.0,
game_scene.player1.vel_x as f32 / 512.0,
game_scene.player1.vel_y as f32 / 512.0,
));
ui.text(format!(
@ -77,7 +77,7 @@ impl LiveDebugger {
));
ui.text(format!(
"Booster fuel: {}", game_scene.player.booster_fuel
"Booster fuel: {}", game_scene.player1.booster_fuel
));
@ -139,13 +139,13 @@ impl LiveDebugger {
if ui.button(im_str!("Load"), [0.0, 0.0]) {
match GameScene::new(state, ctx, self.selected_stage as usize) {
Ok(mut scene) => {
scene.inventory = game_scene.inventory.clone();
scene.player = game_scene.player.clone();
scene.player.x = (scene.stage.map.width / 2 * 16 * 0x200) as isize;
scene.player.y = (scene.stage.map.height / 2 * 16 * 0x200) as isize;
scene.inventory_player1 = game_scene.inventory_player1.clone();
scene.player1 = game_scene.player1.clone();
scene.player1.x = (scene.stage.map.width / 2 * 16 * 0x200) as isize;
scene.player1.y = (scene.stage.map.height / 2 * 16 * 0x200) as isize;
if scene.player.life == 0 {
scene.player.life = scene.player.max_life;
if scene.player1.life == 0 {
scene.player1.life = scene.player1.max_life;
}
state.next_scene = Some(Box::new(scene));
@ -252,19 +252,19 @@ impl LiveDebugger {
}
if CollapsingHeader::new(im_str!("Player condition flags")).default_open(false).build(&ui) {
cond_flags(&ui, &mut game_scene.player.cond);
cond_flags(&ui, &mut game_scene.player1.cond);
}
if CollapsingHeader::new(im_str!("Player equipment")).default_open(false).build(&ui) {
ui.checkbox_flags(im_str!("Booster 0.8"), &mut game_scene.player.equip.0, 1);
ui.checkbox_flags(im_str!("Map System"), &mut game_scene.player.equip.0, 2);
ui.checkbox_flags(im_str!("Arms Barrier"), &mut game_scene.player.equip.0, 4);
ui.checkbox_flags(im_str!("Turbocharge"), &mut game_scene.player.equip.0, 8);
ui.checkbox_flags(im_str!("Air Tank"), &mut game_scene.player.equip.0, 16);
ui.checkbox_flags(im_str!("Booster 2.0"), &mut game_scene.player.equip.0, 32);
ui.checkbox_flags(im_str!("Mimiga Mask"), &mut game_scene.player.equip.0, 64);
ui.checkbox_flags(im_str!("Whimsical Star"), &mut game_scene.player.equip.0, 128);
ui.checkbox_flags(im_str!("Nikumaru Counter"), &mut game_scene.player.equip.0, 256);
ui.checkbox_flags(im_str!("Booster 0.8"), &mut game_scene.player1.equip.0, 1);
ui.checkbox_flags(im_str!("Map System"), &mut game_scene.player1.equip.0, 2);
ui.checkbox_flags(im_str!("Arms Barrier"), &mut game_scene.player1.equip.0, 4);
ui.checkbox_flags(im_str!("Turbocharge"), &mut game_scene.player1.equip.0, 8);
ui.checkbox_flags(im_str!("Air Tank"), &mut game_scene.player1.equip.0, 16);
ui.checkbox_flags(im_str!("Booster 2.0"), &mut game_scene.player1.equip.0, 32);
ui.checkbox_flags(im_str!("Mimiga Mask"), &mut game_scene.player1.equip.0, 64);
ui.checkbox_flags(im_str!("Whimsical Star"), &mut game_scene.player1.equip.0, 128);
ui.checkbox_flags(im_str!("Nikumaru Counter"), &mut game_scene.player1.equip.0, 256);
}
});
}

View file

@ -1,6 +1,7 @@
use crate::common::Rect;
use ggez::{Context, GameResult};
use crate::shared_game_state::SharedGameState;
use crate::input::combined_menu_controller::CombinedMenuController;
pub enum MenuEntry {
Active(String),
@ -177,18 +178,18 @@ impl Menu {
Ok(())
}
pub fn tick(&mut self, state: &mut SharedGameState) -> MenuSelectionResult {
state.update_key_trigger();
pub fn tick(&mut self, controller: &mut CombinedMenuController, state: &mut SharedGameState) -> MenuSelectionResult {
controller.update_trigger();
if state.key_trigger.fire() {
if controller.trigger_back() {
state.sound_manager.play_sfx(5);
return MenuSelectionResult::Canceled;
}
if state.key_trigger.up() || state.key_trigger.down() && !self.entries.is_empty() {
if (controller.trigger_up() || controller.trigger_down()) && !self.entries.is_empty() {
state.sound_manager.play_sfx(1);
loop {
if state.key_trigger.down() {
if controller.trigger_down() {
self.selected += 1;
if self.selected == self.entries.len() {
self.selected = 0;
@ -212,7 +213,7 @@ impl Menu {
}
}
if state.key_trigger.jump() && !self.entries.is_empty() {
if controller.trigger_ok() && !self.entries.is_empty() {
if let Some(entry) = self.entries.get_mut(self.selected) {
match entry {
MenuEntry::Active(_) | MenuEntry::Toggle(_, _) => {

View file

@ -37,6 +37,8 @@ pub trait PhysicalEntity {
fn direction(&self) -> Direction;
fn is_player(&self) -> bool;
fn ignore_tile_44(&self) -> bool { true }
fn player_left_pressed(&self) -> bool { false }
fn player_right_pressed(&self) -> bool { false }
fn judge_hit_block(&mut self, state: &mut SharedGameState, x: isize, y: isize) {
let bounds_x = if self.is_player() { 5 } else { 8 };
@ -53,7 +55,7 @@ pub trait PhysicalEntity {
self.set_vel_x(-0x180);
}
if !state.key_state.left() && self.vel_x() < 0 {
if !self.player_left_pressed() && self.vel_x() < 0 {
self.set_vel_x(0);
}
}
@ -73,7 +75,7 @@ pub trait PhysicalEntity {
self.set_vel_x(0x180);
}
if !state.key_state.right() && self.vel_x() > 0 {
if !self.player_right_pressed() && self.vel_x() > 0 {
self.set_vel_x(0);
}
}

View file

@ -1,17 +1,20 @@
use std::clone::Clone;
use ggez::{Context, GameResult};
use num_derive::FromPrimitive;
use num_traits::clamp;
use num_traits::FromPrimitive;
use crate::caret::CaretType;
use crate::common::{Condition, Direction, Equipment, Flag, interpolate_fix9_scale, Rect};
use crate::entity::GameEntity;
use crate::frame::Frame;
use ggez::{Context, GameResult};
use crate::input::dummy_player_controller::DummyPlayerController;
use crate::input::player_controller::PlayerController;
use crate::npc::NPCMap;
use crate::shared_game_state::SharedGameState;
mod player_hit;
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)]
#[repr(u8)]
pub enum ControlMode {
@ -31,6 +34,19 @@ pub enum PlayerAppearance {
Curly,
}
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum TargetPlayer {
Player1,
Player2,
}
impl TargetPlayer {
#[inline]
pub fn index(self) -> usize {
self as usize
}
}
#[derive(Clone)]
pub struct Player {
pub x: isize,
@ -61,6 +77,7 @@ pub struct Player {
pub air_counter: u16,
pub air: u16,
pub appearance: PlayerAppearance,
pub controller: Box<dyn PlayerController>,
weapon_offset_y: i8,
index_x: isize,
index_y: isize,
@ -113,6 +130,7 @@ impl Player {
air_counter: 0,
air: 0,
appearance: PlayerAppearance::Quote,
controller: Box::new(DummyPlayerController::new()),
bubble: 0,
damage_counter: 0,
damage_taken: 0,
@ -181,23 +199,33 @@ impl Player {
}
if state.control_flags.control_enabled() {
if state.key_trigger.only_down() && state.key_state.only_down() && !self.cond.interacted() && !state.control_flags.interactions_disabled() {
let trigger_only_down = self.controller.trigger_down()
&& !self.controller.trigger_up()
&& !self.controller.trigger_left()
&& !self.controller.trigger_right();
let only_down = self.controller.move_down()
&& !self.controller.move_up()
&& !self.controller.move_left()
&& !self.controller.move_right();
if trigger_only_down && only_down && !self.cond.interacted() && !state.control_flags.interactions_disabled() {
self.cond.set_interacted(true);
self.question = true;
} else {
if state.key_state.left() && self.vel_x > -physics.max_dash {
if self.controller.move_left() && self.vel_x > -physics.max_dash {
self.vel_x -= physics.dash_ground;
}
if state.key_state.right() && self.vel_x < physics.max_dash {
if self.controller.move_right() && self.vel_x < physics.max_dash {
self.vel_x += physics.dash_ground;
}
if state.key_state.left() {
if self.controller.move_left() {
self.direction = Direction::Left;
}
if state.key_state.right() {
if self.controller.move_right() {
self.direction = Direction::Right;
}
}
@ -221,7 +249,7 @@ impl Player {
}
} else { // air movement
if state.control_flags.control_enabled() {
if state.key_trigger.jump() && self.booster_fuel != 0 {
if self.controller.trigger_jump() && self.booster_fuel != 0 {
if self.equip.has_booster_0_8() {
self.booster_switch = 1;
@ -229,19 +257,19 @@ impl Player {
self.vel_y /= 2;
}
} else if state.settings.infinite_booster || self.equip.has_booster_2_0() {
if state.key_state.up() {
if self.controller.move_up() {
self.booster_switch = 2;
self.vel_x = 0;
self.vel_y = state.constants.booster.b2_0_up;
} else if state.key_state.left() {
} else if self.controller.move_left() {
self.booster_switch = 1;
self.vel_x = state.constants.booster.b2_0_left;
self.vel_y = 0;
} else if state.key_state.right() {
} else if self.controller.move_right() {
self.booster_switch = 1;
self.vel_x = state.constants.booster.b2_0_right;
self.vel_y = 0;
} else if state.key_state.down() {
} else if self.controller.move_down() {
self.booster_switch = 3;
self.vel_x = 0;
self.vel_y = state.constants.booster.b2_0_down;
@ -253,25 +281,25 @@ impl Player {
}
}
if state.key_state.left() && self.vel_x > -physics.max_dash {
if self.controller.move_left() && self.vel_x > -physics.max_dash {
self.vel_x -= physics.dash_air;
}
if state.key_state.right() && self.vel_x < physics.max_dash {
if self.controller.move_right() && self.vel_x < physics.max_dash {
self.vel_x += physics.dash_air;
}
if state.key_state.left() {
if self.controller.look_left() {
self.direction = Direction::Left;
}
if state.key_state.right() {
if self.controller.look_right() {
self.direction = Direction::Right;
}
}
if (state.settings.infinite_booster || self.equip.has_booster_2_0()) && self.booster_switch != 0
&& (!state.key_state.jump() || self.booster_fuel == 0) {
&& (!self.controller.jump() || self.booster_fuel == 0) {
match self.booster_switch {
1 => { self.vel_x /= 2 }
2 => { self.vel_y /= 2 }
@ -279,24 +307,31 @@ impl Player {
}
}
if self.booster_fuel == 0 || !state.key_state.jump() {
if self.booster_fuel == 0 || !self.controller.jump() {
self.booster_switch = 0;
}
}
// jumping
if state.control_flags.control_enabled() {
self.up = state.key_state.up();
self.down = state.key_state.down() && !self.flags.hit_bottom_wall();
self.up = self.controller.move_up();
self.down = self.controller.move_down() && !self.flags.hit_bottom_wall();
if state.key_trigger.jump() && (self.flags.hit_bottom_wall() || self.flags.hit_right_slope() || self.flags.hit_left_slope()) && !self.flags.force_up() {
if self.controller.trigger_jump() && (self.flags.hit_bottom_wall()
|| self.flags.hit_right_slope()
|| self.flags.hit_left_slope())
&& !self.flags.force_up() {
self.vel_y = -physics.jump;
state.sound_manager.play_sfx(15);
}
}
// stop interacting when moved
if state.control_flags.control_enabled() && (state.key_state.left() || state.key_state.right() || state.key_state.up() || state.key_state.jump() || state.key_state.fire()) {
if state.control_flags.control_enabled() && (self.controller.move_left()
|| self.controller.move_right()
|| self.controller.move_up()
|| self.controller.jump()
|| self.controller.shoot()) {
self.cond.set_interacted(false);
}
@ -334,7 +369,7 @@ impl Player {
self.vel_x += 0x20; // 0.1fix9
}
if state.key_trigger.jump() || self.booster_fuel % 3 == 1 {
if self.controller.trigger_jump() || self.booster_fuel % 3 == 1 {
if self.direction == Direction::Left || self.direction == Direction::Right {
state.create_caret(self.x + 0x400, self.y + 0x400, CaretType::Exhaust, self.direction.opposite());
}
@ -344,12 +379,12 @@ impl Player {
2 => {
self.vel_y -= 0x20;
if state.key_trigger.jump() || self.booster_fuel % 3 == 1 {
if self.controller.trigger_jump() || self.booster_fuel % 3 == 1 {
state.create_caret(self.x, self.y + 6 * 0x200, CaretType::Exhaust, Direction::Bottom);
state.sound_manager.play_sfx(113);
}
}
3 if state.key_trigger.jump() || self.booster_fuel % 3 == 1 => {
3 if self.controller.trigger_jump() || self.booster_fuel % 3 == 1 => {
state.create_caret(self.x, self.y + 6 * 0x200, CaretType::Exhaust, Direction::Up);
state.sound_manager.play_sfx(113);
}
@ -369,13 +404,13 @@ impl Player {
if self.flags.hit_top_wall() {
self.vel_y = 0x200; // 1.0fix9
}
} else if self.vel_y < 0 && state.control_flags.control_enabled() && state.key_state.jump() {
} else if self.vel_y < 0 && state.control_flags.control_enabled() && self.controller.jump() {
self.vel_y += physics.gravity_air;
} else {
self.vel_y += physics.gravity_ground;
}
if !state.control_flags.control_enabled() || !state.key_trigger.jump() {
if !state.control_flags.control_enabled() || !self.controller.trigger_jump() {
if self.flags.hit_right_slope() && self.vel_x < 0 {
self.vel_y = -self.vel_x;
}
@ -442,12 +477,12 @@ impl Player {
// camera
self.index_x = clamp(self.index_x + self.direction.vector_x() * 0x200, -0x8000, 0x8000);
if state.control_flags.control_enabled() && state.key_state.up() {
if state.control_flags.control_enabled() && self.controller.move_up() {
self.index_y -= 0x200; // 1.0fix9
if self.index_y < -0x8000 { // -64.0fix9
self.index_y = -0x8000;
}
} else if state.control_flags.control_enabled() && state.key_state.down() {
} else if state.control_flags.control_enabled() && self.controller.move_down() {
self.index_y += 0x200; // 1.0fix9
if self.index_y > 0x8000 { // -64.0fix9
self.index_y = 0x8000;
@ -487,7 +522,7 @@ impl Player {
if self.flags.hit_bottom_wall() {
if self.cond.interacted() {
self.anim_num = 11;
} else if state.control_flags.control_enabled() && state.key_state.up() && (state.key_state.left() || state.key_state.right()) {
} else if state.control_flags.control_enabled() && self.controller.move_up() && (self.controller.move_left() || self.controller.move_right()) {
self.cond.set_fallen(true);
self.anim_counter += 1;
@ -503,7 +538,7 @@ impl Player {
if self.anim_num > 9 || self.anim_num < 6 {
self.anim_num = 6;
}
} else if state.control_flags.control_enabled() && (state.key_state.left() || state.key_state.right()) {
} else if state.control_flags.control_enabled() && (self.controller.move_left() || self.controller.move_right()) {
self.cond.set_fallen(true);
self.anim_counter += 1;
@ -519,7 +554,7 @@ impl Player {
if self.anim_num > 4 || self.anim_num < 1 {
self.anim_num = 1;
}
} else if state.control_flags.control_enabled() && state.key_state.up() {
} else if state.control_flags.control_enabled() && self.controller.move_up() {
if self.cond.fallen() {
state.sound_manager.play_sfx(24);
}
@ -534,9 +569,9 @@ impl Player {
self.cond.set_fallen(false);
self.anim_num = 0;
}
} else if state.key_state.up() {
} else if self.controller.look_up() {
self.anim_num = 6;
} else if state.key_state.down() {
} else if self.controller.look_down() {
self.anim_num = 10;
} else {
self.anim_num = if self.vel_y > 0 { 1 } else { 3 };

View file

@ -79,6 +79,14 @@ impl PhysicalEntity for Player {
fn is_player(&self) -> bool {
true
}
fn player_left_pressed(&self) -> bool {
self.controller.move_left()
}
fn player_right_pressed(&self) -> bool {
self.controller.move_right()
}
}
impl Player {

View file

@ -53,14 +53,14 @@ impl GameProfile {
state.sound_manager.play_song(self.current_song as usize, &state.constants, ctx);
game_scene.inventory.current_weapon = self.current_weapon as u16;
game_scene.inventory.current_item = self.current_item as u16;
game_scene.inventory_player1.current_weapon = self.current_weapon as u16;
game_scene.inventory_player1.current_item = self.current_item as u16;
for weapon in self.weapon_data.iter() {
if weapon.weapon_id == 0 { continue; }
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon.weapon_id as u8);
if let Some(wtype) = weapon_type {
let w = game_scene.inventory.add_weapon(wtype, weapon.max_ammo as u16);
let w = game_scene.inventory_player1.add_weapon(wtype, weapon.max_ammo as u16);
w.ammo = weapon.ammo as u16;
w.level = match weapon.level {
2 => { WeaponLevel::Level2 }
@ -74,7 +74,7 @@ impl GameProfile {
for item in self.items.iter().copied() {
if item == 0 { break; }
game_scene.inventory.add_item(item as u16);
game_scene.inventory_player1.add_item(item as u16);
}
for slot in self.teleporter_slots.iter() {
@ -94,32 +94,32 @@ impl GameProfile {
if flags & 0b10000000 != 0 { state.game_flags.set(idx * 8 + 7, true); }
}
game_scene.player.equip.0 = self.equipment as u16;
game_scene.player.cond.0 = 0x80;
game_scene.player1.equip.0 = self.equipment as u16;
game_scene.player1.cond.0 = 0x80;
game_scene.player.x = self.pos_x as isize;
game_scene.player.y = self.pos_y as isize;
game_scene.player1.x = self.pos_x as isize;
game_scene.player1.y = self.pos_y as isize;
game_scene.player.control_mode = if self.control_mode == 1 { ControlMode::IronHead } else { ControlMode::Normal };
game_scene.player.direction = self.direction;
game_scene.player.life = self.life;
game_scene.player.max_life = self.max_life;
game_scene.player.stars = clamp(self.stars, 0, 3) as u8;
game_scene.player1.control_mode = if self.control_mode == 1 { ControlMode::IronHead } else { ControlMode::Normal };
game_scene.player1.direction = self.direction;
game_scene.player1.life = self.life;
game_scene.player1.max_life = self.max_life;
game_scene.player1.stars = clamp(self.stars, 0, 3) as u8;
}
pub fn dump(state: &mut SharedGameState, game_scene: &mut GameScene) -> GameProfile {
let current_map = game_scene.stage_id as u32;
let current_song = state.sound_manager.current_song() as u32;
let pos_x = game_scene.player.x as i32;
let pos_y = game_scene.player.y as i32;
let direction = game_scene.player.direction;
let max_life = game_scene.player.max_life;
let stars = game_scene.player.stars as u16;
let life = game_scene.player.life;
let current_weapon = game_scene.inventory.current_weapon as u32;
let current_item = game_scene.inventory.current_item as u32;
let equipment = game_scene.player.equip.0 as u32;
let control_mode = game_scene.player.control_mode as u32;
let pos_x = game_scene.player1.x as i32;
let pos_y = game_scene.player1.y as i32;
let direction = game_scene.player1.direction;
let max_life = game_scene.player1.max_life;
let stars = game_scene.player1.stars as u16;
let life = game_scene.player1.life;
let current_weapon = game_scene.inventory_player1.current_weapon as u32;
let current_item = game_scene.inventory_player1.current_item as u32;
let equipment = game_scene.player1.equip.0 as u32;
let control_mode = game_scene.player1.control_mode as u32;
let counter = 0; // TODO
let mut weapon_data = [
WeaponData { weapon_id: 0, level: 0, exp: 0, max_ammo: 0, ammo: 0 },
@ -144,7 +144,7 @@ impl GameProfile {
];
for (idx, weap) in weapon_data.iter_mut().enumerate() {
if let Some(weapon) = game_scene.inventory.get_weapon(idx) {
if let Some(weapon) = game_scene.inventory_player1.get_weapon(idx) {
weap.weapon_id = weapon.wtype as u32;
weap.level = weapon.level as u32;
weap.exp = weapon.experience as u32;
@ -154,7 +154,7 @@ impl GameProfile {
}
for (idx, item) in items.iter_mut().enumerate() {
if let Some(sitem) = game_scene.inventory.get_item_idx(idx) {
if let Some(sitem) = game_scene.inventory_player1.get_item_idx(idx) {
*item = sitem.0 as u32;
}
}

View file

@ -1,5 +1,6 @@
use std::cell::Cell;
/// Deterministic XorShift-based random number generator
pub struct RNG(Cell<(u64, u64, u64, u64)>);
#[inline]
@ -48,6 +49,14 @@ impl RNG {
self.next_u64() as u32
}
pub fn dump_state(&self) -> (u64, u64, u64, u64) {
self.0.get()
}
pub fn load_state(&mut self, saved_state: (u64, u64, u64, u64)) {
self.0.replace(saved_state);
}
pub fn range(&self, range: std::ops::Range<i32>) -> i32 {
range.start.wrapping_add((self.next_u32() >> 2) as i32 % (range.end.wrapping_sub(range.start).wrapping_add(1)))
}

View file

@ -10,6 +10,7 @@ use crate::bullet::BulletManager;
use crate::caret::CaretType;
use crate::common::{Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect};
use crate::components::boss_life_bar::BossLifeBar;
use crate::components::stage_select::StageSelect;
use crate::entity::GameEntity;
use crate::frame::{Frame, UpdateTarget};
use crate::inventory::{Inventory, TakeExperienceResult};
@ -30,13 +31,15 @@ pub struct GameScene {
pub tick: usize,
pub stage: Stage,
pub boss_life_bar: BossLifeBar,
pub stage_select: StageSelect,
pub frame: Frame,
pub player: Player,
pub inventory: Inventory,
pub player1: Player,
pub player2: Player,
pub inventory_player1: Inventory,
pub inventory_player2: Inventory,
pub stage_id: usize,
pub npc_map: NPCMap,
pub bullet_manager: BulletManager,
pub current_teleport_slot: u8,
pub intro_mode: bool,
water_visible: bool,
tex_background_name: String,
@ -44,7 +47,6 @@ pub struct GameScene {
life_bar: u16,
life_bar_counter: u16,
map_name_counter: u16,
stage_select_text_y_pos: usize,
weapon_x_pos: isize,
}
@ -77,9 +79,12 @@ impl GameScene {
Ok(Self {
tick: 0,
stage,
player: Player::new(state),
inventory: Inventory::new(),
player1: Player::new(state),
player2: Player::new(state),
inventory_player1: Inventory::new(),
inventory_player2: Inventory::new(),
boss_life_bar: BossLifeBar::new(),
stage_select: StageSelect::new(),
frame: Frame {
x: 0,
y: 0,
@ -93,7 +98,6 @@ impl GameScene {
stage_id: id,
npc_map: NPCMap::new(),
bullet_manager: BulletManager::new(),
current_teleport_slot: 0,
intro_mode: false,
water_visible: true,
tex_background_name,
@ -101,7 +105,6 @@ impl GameScene {
life_bar: 0,
life_bar_counter: 0,
map_name_counter: 0,
stage_select_text_y_pos: 54,
weapon_x_pos: 16,
})
}
@ -127,8 +130,8 @@ impl GameScene {
fn draw_hud(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
// none
let weap_x = self.weapon_x_pos as f32;
let (ammo, max_ammo) = self.inventory.get_current_ammo();
let (xp, max_xp, max_level) = self.inventory.get_current_max_exp(&state.constants);
let (ammo, max_ammo) = self.inventory_player1.get_current_ammo();
let (xp, max_xp, max_level) = self.inventory_player1.get_current_max_exp(&state.constants);
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
if max_ammo == 0 {
@ -159,20 +162,20 @@ impl GameScene {
&Rect::new_size(0, 80, bar_width, 8));
}
if self.player.max_life != 0 {
if self.player1.max_life != 0 {
// life box
batch.add_rect(16.0, 40.0,
&Rect::new_size(0, 40, 64, 8));
// yellow bar
batch.add_rect(40.0, 40.0,
&Rect::new_size(0, 32, (self.life_bar * 40) / self.player.max_life, 8));
&Rect::new_size(0, 32, (self.life_bar * 40) / self.player1.max_life, 8));
// life
batch.add_rect(40.0, 40.0,
&Rect::new_size(0, 24, (self.player.life * 40) / self.player.max_life, 8));
&Rect::new_size(0, 24, (self.player1.life * 40) / self.player1.max_life, 8));
}
if self.player.air_counter > 0 {
let rect = if self.player.air % 30 > 10 {
if self.player1.air_counter > 0 {
let rect = if self.player1.air % 30 > 10 {
Rect::new_size(112, 72, 32, 8)
} else {
Rect::new_size(112, 80, 32, 8)
@ -185,9 +188,9 @@ impl GameScene {
batch.draw(ctx)?;
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
let weapon_count = self.inventory.get_weapon_count();
let weapon_count = self.inventory_player1.get_weapon_count();
if weapon_count != 0 {
let current_weapon = self.inventory.get_current_weapon_idx() as isize;
let current_weapon = self.inventory_player1.get_current_weapon_idx() as isize;
let mut rect = Rect::new(0, 0, 0, 16);
for a in 0..weapon_count {
@ -205,7 +208,7 @@ impl GameScene {
pos_x -= 48.0;
}
if let Some(weapon) = self.inventory.get_weapon(a) {
if let Some(weapon) = self.inventory_player1.get_weapon(a) {
rect.left = weapon.wtype as u16 * 16;
rect.right = rect.left + 16;
batch.add_rect(pos_x, 16.0, &rect);
@ -215,17 +218,17 @@ impl GameScene {
batch.draw(ctx)?;
if self.player.air_counter > 0 && self.player.air_counter % 6 < 4 {
if self.player1.air_counter > 0 && self.player1.air_counter % 6 < 4 {
self.draw_number((state.canvas_size.0 / 2.0).floor() + 8.0,
(state.canvas_size.1 / 2.0).floor(),
(self.player.air / 10) as usize, Alignment::Left, state, ctx)?;
(self.player1.air / 10) as usize, Alignment::Left, state, ctx)?;
}
if max_ammo != 0 {
self.draw_number(weap_x + 64.0, 16.0, ammo as usize, Alignment::Right, state, ctx)?;
self.draw_number(weap_x + 64.0, 24.0, max_ammo as usize, Alignment::Right, state, ctx)?;
}
self.draw_number(weap_x + 24.0, 32.0, self.inventory.get_current_level() as usize, Alignment::Right, state, ctx)?;
self.draw_number(weap_x + 24.0, 32.0, self.inventory_player1.get_current_level() as usize, Alignment::Right, state, ctx)?;
self.draw_number(40.0, 40.0, self.life_bar as usize, Alignment::Right, state, ctx)?;
Ok(())
@ -614,9 +617,9 @@ impl GameScene {
let scale = state.scale;
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/lightmap/spot")?;
if !self.player.cond.hidden() && self.inventory.get_current_weapon().is_some() {
self.draw_light(fix9_scale(self.player.x - self.frame.x, scale),
fix9_scale(self.player.y - self.frame.y, scale),
if !self.player1.cond.hidden() && self.inventory_player1.get_current_weapon().is_some() {
self.draw_light(fix9_scale(self.player1.x - self.frame.x, scale),
fix9_scale(self.player1.y - self.frame.y, scale),
4.0, (140, 140, 140), batch);
}
@ -943,7 +946,7 @@ impl GameScene {
// todo show damage
}
if self.player.cond.alive() && npc.npc_flags.event_when_killed() {
if self.player1.cond.alive() && npc.npc_flags.event_when_killed() {
state.control_flags.set_tick_world(true);
state.control_flags.set_interactions_disabled(true);
state.textscript_vm.start_script(npc.event_num);
@ -1030,7 +1033,7 @@ impl GameScene {
if npc.life == 0 {
npc.life = npc.id;
if self.player.cond.alive() && npc.npc_flags.event_when_killed() {
if self.player1.cond.alive() && npc.npc_flags.event_when_killed() {
state.control_flags.set_tick_world(true);
state.control_flags.set_interactions_disabled(true);
state.textscript_vm.start_script(npc.event_num);
@ -1078,51 +1081,48 @@ impl GameScene {
}
if !dead_npcs.is_empty() {
let missile = self.inventory.has_weapon(WeaponType::MissileLauncher)
| self.inventory.has_weapon(WeaponType::SuperMissileLauncher);
self.npc_map.process_dead_npcs(&dead_npcs, missile, &self.player, state);
let missile = self.inventory_player1.has_weapon(WeaponType::MissileLauncher)
|| self.inventory_player1.has_weapon(WeaponType::SuperMissileLauncher);
self.npc_map.process_dead_npcs(&dead_npcs, missile, &self.player1, state);
self.npc_map.garbage_collect();
}
}
fn tick_world(&mut self, state: &mut SharedGameState) -> GameResult {
self.stage_select_text_y_pos = 54;
self.current_teleport_slot = 0;
self.player.current_weapon = {
if let Some(weapon) = self.inventory.get_current_weapon_mut() {
self.player1.current_weapon = {
if let Some(weapon) = self.inventory_player1.get_current_weapon_mut() {
weapon.wtype as u8
} else {
0
}
};
self.player.tick(state, ())?;
self.player1.tick(state, ())?;
if self.player.damage > 0 {
let xp_loss = self.player.damage * if self.player.equip.has_arms_barrier() { 1 } else { 2 };
match self.inventory.take_xp(xp_loss, state) {
TakeExperienceResult::LevelDown if self.player.life > 0 => {
state.create_caret(self.player.x, self.player.y, CaretType::LevelUp, Direction::Right);
if self.player1.damage > 0 {
let xp_loss = self.player1.damage * if self.player1.equip.has_arms_barrier() { 1 } else { 2 };
match self.inventory_player1.take_xp(xp_loss, state) {
TakeExperienceResult::LevelDown if self.player1.life > 0 => {
state.create_caret(self.player1.x, self.player1.y, CaretType::LevelUp, Direction::Right);
}
_ => {}
}
self.player.damage = 0;
self.player1.damage = 0;
}
for npc_cell in self.npc_map.npcs.values() {
let mut npc = npc_cell.borrow_mut();
if npc.cond.alive() {
npc.tick(state, (&mut self.player, &self.npc_map.npcs, &mut self.stage))?;
npc.tick(state, (&mut self.player1, &self.npc_map.npcs, &mut self.stage))?;
}
}
self.npc_map.boss_map.tick(state, (&mut self.player, &self.npc_map.npcs, &mut self.stage))?;
self.npc_map.process_npc_changes(&self.player, state);
self.npc_map.boss_map.tick(state, (&mut self.player1, &self.npc_map.npcs, &mut self.stage))?;
self.npc_map.process_npc_changes(&self.player1, state);
self.npc_map.garbage_collect();
self.player.tick_map_collisions(state, &mut self.stage);
self.player.tick_npc_collisions(state, &mut self.npc_map, &mut self.inventory);
self.player1.tick_map_collisions(state, &mut self.stage);
self.player1.tick_npc_collisions(state, &mut self.npc_map, &mut self.inventory_player1);
for npc_cell in self.npc_map.npcs.values() {
let mut npc = npc_cell.borrow_mut();
@ -1136,19 +1136,19 @@ impl GameScene {
npc.tick_map_collisions(state, &mut self.stage);
}
}
self.npc_map.process_npc_changes(&self.player, state);
self.npc_map.process_npc_changes(&self.player1, state);
self.npc_map.garbage_collect();
self.tick_npc_bullet_collissions(state);
self.npc_map.process_npc_changes(&self.player, state);
self.npc_map.process_npc_changes(&self.player1, state);
self.bullet_manager.tick_bullets(state, &self.player, &mut self.stage);
self.bullet_manager.tick_bullets(state, &self.player1, &mut self.stage);
state.tick_carets();
match self.frame.update_target {
UpdateTarget::Player => {
self.frame.target_x = self.player.target_x;
self.frame.target_y = self.player.target_y;
self.frame.target_x = self.player1.target_x;
self.frame.target_y = self.player1.target_y;
}
UpdateTarget::NPC(npc_id) => {
if let Some(npc_cell) = self.npc_map.npcs.get(&npc_id) {
@ -1172,28 +1172,28 @@ impl GameScene {
self.frame.update(state, &self.stage);
if state.control_flags.control_enabled() {
if let Some(weapon) = self.inventory.get_current_weapon_mut() {
weapon.shoot_bullet(&self.player, &mut self.bullet_manager, state);
if let Some(weapon) = self.inventory_player1.get_current_weapon_mut() {
weapon.shoot_bullet(&self.player1, &mut self.bullet_manager, state);
}
if state.key_trigger.weapon_next() {
if self.player1.controller.trigger_next_weapon() {
state.sound_manager.play_sfx(4);
self.inventory.next_weapon();
self.inventory_player1.next_weapon();
self.weapon_x_pos = 32;
}
if state.key_trigger.weapon_prev() {
if self.player1.controller.trigger_prev_weapon() {
state.sound_manager.play_sfx(4);
self.inventory.prev_weapon();
self.inventory_player1.prev_weapon();
self.weapon_x_pos = 0;
}
// update health bar
if self.life_bar < self.player.life as u16 {
self.life_bar = self.player.life as u16;
if self.life_bar < self.player1.life as u16 {
self.life_bar = self.player1.life as u16;
}
if self.life_bar > self.player.life as u16 {
if self.life_bar > self.player1.life as u16 {
self.life_bar_counter += 1;
if self.life_bar_counter > 30 {
self.life_bar -= 1;
@ -1208,88 +1208,6 @@ impl GameScene {
Ok(())
}
fn tick_stage_select(&mut self, state: &mut SharedGameState) -> GameResult {
let slot_count = state.teleporter_slots.iter()
.filter(|&&(index, _event_num)| index != 0)
.count();
if self.stage_select_text_y_pos > 46 {
self.stage_select_text_y_pos -= 1;
}
if state.key_trigger.left() {
if self.current_teleport_slot == 0 {
self.current_teleport_slot = slot_count.saturating_sub(1) as u8;
} else {
self.current_teleport_slot -= 1;
}
} else if state.key_trigger.right() {
if self.current_teleport_slot == slot_count.saturating_sub(1) as u8 {
self.current_teleport_slot = 0;
} else {
self.current_teleport_slot += 1;
}
}
if state.key_trigger.left() || state.key_trigger.right() {
state.sound_manager.play_sfx(1);
if let Some(&(index, _event_num)) = state.teleporter_slots.get(self.current_teleport_slot as usize) {
state.textscript_vm.start_script(1000 + index);
} else {
state.textscript_vm.start_script(1000);
}
}
if state.key_trigger.jump() | state.key_trigger.fire() {
state.textscript_vm.set_mode(ScriptMode::Map);
state.control_flags.set_tick_world(true);
state.control_flags.set_control_enabled(true);
state.control_flags.set_interactions_disabled(false);
if state.key_trigger.jump() {
if let Some(&(_index, event_num)) = state.teleporter_slots.get(self.current_teleport_slot as usize) {
state.textscript_vm.start_script(event_num);
}
}
}
Ok(())
}
fn draw_stage_select(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "StageImage")?;
let slot_count = state.teleporter_slots.iter()
.filter(|&&(index, _event_num)| index != 0)
.count();
let slot_offset = ((state.canvas_size.0 - 40.0 * slot_count as f32) / 2.0).floor();
let mut slot_rect = Rect::new(0, 0, 0, 0);
for i in 0..slot_count {
let index = state.teleporter_slots[i].0;
slot_rect.left = 32 * (index as u16 % 8);
slot_rect.top = 16 * (index as u16 / 8);
slot_rect.right = slot_rect.left + 32;
slot_rect.bottom = slot_rect.top + 16;
batch.add_rect(slot_offset + i as f32 * 40.0, 64.0, &slot_rect);
}
batch.draw(ctx)?;
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
batch.add_rect(128.0, self.stage_select_text_y_pos as f32, &state.constants.textscript.stage_select_text);
if slot_count > 0 {
batch.add_rect(slot_offset + self.current_teleport_slot as f32 * 40.0, 64.0, &state.constants.textscript.cursor[self.tick / 2 % 2]);
}
batch.draw(ctx)?;
Ok(())
}
fn draw_debug_npc(&self, npc: &NPC, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
if npc.x < (self.frame.x - 128 - npc.display_bounds.width() as isize * 0x200)
|| npc.x > (self.frame.x + 128 + (state.canvas_size.0 as isize + npc.display_bounds.width() as isize) * 0x200)
@ -1351,15 +1269,18 @@ impl GameScene {
impl Scene for GameScene {
fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let seed = (self.player.max_life as i32)
.wrapping_add(self.player.x as i32)
.wrapping_add(self.player.y as i32)
let seed = (self.player1.max_life as i32)
.wrapping_add(self.player1.x as i32)
.wrapping_add(self.player1.y as i32)
.wrapping_add(self.stage_id as i32)
.wrapping_mul(7);
state.game_rng = RNG::new(seed);
state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, &state.constants, ctx)?);
state.textscript_vm.suspend = false;
self.player1.controller = state.settings.create_player1_controller();
self.player2.controller = state.settings.create_player2_controller();
let npcs = self.stage.load_npcs(&state.base_path, ctx)?;
for npc_data in npcs.iter() {
log::info!("creating npc: {:?}", npc_data);
@ -1384,32 +1305,33 @@ impl Scene for GameScene {
if state.constants.is_cs_plus {
match state.season {
Season::Halloween => self.player.appearance = PlayerAppearance::HalloweenQuote,
Season::Christmas => self.player.appearance = PlayerAppearance::ReindeerQuote,
Season::Halloween => self.player1.appearance = PlayerAppearance::HalloweenQuote,
Season::Christmas => self.player1.appearance = PlayerAppearance::ReindeerQuote,
_ => {}
}
}
self.npc_map.boss_map.boss_type = self.stage.data.boss_no as u16;
self.player.target_x = self.player.x;
self.player.target_y = self.player.y;
self.frame.target_x = self.player.target_x;
self.frame.target_y = self.player.target_y;
self.player1.target_x = self.player1.x;
self.player1.target_y = self.player1.y;
self.frame.target_x = self.player1.target_x;
self.frame.target_y = self.player1.target_y;
self.frame.immediate_update(state, &self.stage);
Ok(())
}
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
state.update_key_trigger();
self.player1.controller.update(state, ctx)?;
self.player1.controller.update_trigger();
if self.intro_mode && (state.key_trigger.jump() || self.tick >= 500) {
if self.intro_mode && (self.player1.controller.trigger_menu_ok() || self.tick >= 500) {
state.next_scene = Some(Box::new(TitleScene::new()));
}
match state.textscript_vm.mode {
ScriptMode::Map if state.control_flags.tick_world() => self.tick_world(state)?,
ScriptMode::StageSelect => self.tick_stage_select(state)?,
ScriptMode::StageSelect => self.stage_select.tick(state, (&self.player1, &self.player2))?,
_ => {}
}
@ -1447,8 +1369,8 @@ impl Scene for GameScene {
fn draw_tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
self.frame.prev_x = self.frame.x;
self.frame.prev_y = self.frame.y;
self.player.prev_x = self.player.x;
self.player.prev_y = self.player.y;
self.player1.prev_x = self.player1.x;
self.player1.prev_y = self.player1.y;
for npc_cell in self.npc_map.npcs.values() {
let mut npc = npc_cell.borrow_mut();
@ -1509,7 +1431,7 @@ impl Scene for GameScene {
npc.draw(state, ctx, &self.frame)?;
}
self.draw_bullets(state, ctx)?;
self.player.draw(state, ctx, &self.frame)?;
self.player1.draw(state, ctx, &self.frame)?;
if state.settings.shader_effects && self.water_visible {
self.draw_water(state, ctx)?;
}
@ -1534,7 +1456,7 @@ impl Scene for GameScene {
}
if state.textscript_vm.mode == ScriptMode::StageSelect {
self.draw_stage_select(state, ctx)?;
self.stage_select.draw(state, ctx, &self.frame)?;
}
self.draw_fade(state, ctx)?;

View file

@ -5,6 +5,7 @@ use crate::common::Rect;
use crate::menu::{Menu, MenuEntry, MenuSelectionResult};
use crate::scene::Scene;
use crate::shared_game_state::{SharedGameState, TimingMode};
use crate::input::combined_menu_controller::CombinedMenuController;
#[derive(PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
@ -17,6 +18,7 @@ enum CurrentMenu {
pub struct TitleScene {
tick: usize,
controller: CombinedMenuController,
current_menu: CurrentMenu,
main_menu: Menu,
option_menu: Menu,
@ -26,6 +28,7 @@ impl TitleScene {
pub fn new() -> Self {
Self {
tick: 0,
controller: CombinedMenuController::new(),
current_menu: CurrentMenu::MainMenu,
main_menu: Menu::new(0, 0, 100, 1 * 14 + 6),
option_menu: Menu::new(0, 0, 180, 1 * 14 + 6),
@ -82,35 +85,42 @@ static COPYRIGHT_NICALIS_SWITCH: &str = "@2017 NICALIS INC.";
static DISCORD_LINK: &str = "https://discord.gg/fbRsNNB";
impl Scene for TitleScene {
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
if self.tick == 0 {
state.sound_manager.play_song(24, &state.constants, ctx)?;
self.main_menu.push_entry(MenuEntry::Active("New game".to_string()));
self.main_menu.push_entry(MenuEntry::Active("Load game".to_string()));
self.main_menu.push_entry(MenuEntry::Active("Options".to_string()));
self.main_menu.push_entry(MenuEntry::Disabled("Editor".to_string()));
self.main_menu.push_entry(MenuEntry::Active("Quit".to_string()));
self.main_menu.height = self.main_menu.entries.len() as u16 * 14 + 6;
fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
self.controller.add(state.settings.create_player1_controller());
self.controller.add(state.settings.create_player2_controller());
self.option_menu.push_entry(MenuEntry::Toggle("Original timing (50TPS)".to_string(), state.timing_mode == TimingMode::_50Hz));
self.option_menu.push_entry(MenuEntry::Toggle("Lighting effects".to_string(), state.settings.shader_effects));
if state.constants.supports_og_textures {
self.option_menu.push_entry(MenuEntry::Toggle("Original textures".to_string(), state.settings.original_textures));
} else {
self.option_menu.push_entry(MenuEntry::Disabled("Original textures".to_string()));
}
state.sound_manager.play_song(24, &state.constants, ctx)?;
self.main_menu.push_entry(MenuEntry::Active("New game".to_string()));
self.main_menu.push_entry(MenuEntry::Active("Load game".to_string()));
self.main_menu.push_entry(MenuEntry::Active("Options".to_string()));
self.main_menu.push_entry(MenuEntry::Disabled("Editor".to_string()));
self.main_menu.push_entry(MenuEntry::Active("Quit".to_string()));
self.main_menu.height = self.main_menu.entries.len() as u16 * 14 + 6;
if state.constants.is_cs_plus {
self.option_menu.push_entry(MenuEntry::Toggle("Seasonal textures".to_string(), state.settings.seasonal_textures));
} else {
self.option_menu.push_entry(MenuEntry::Disabled("Seasonal textures".to_string()));
}
self.option_menu.push_entry(MenuEntry::Active("Join our Discord".to_string()));
self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned()));
self.option_menu.push_entry(MenuEntry::Active("Back".to_string()));
self.option_menu.height = self.option_menu.entries.len() as u16 * 14 + 6;
self.option_menu.push_entry(MenuEntry::Toggle("Original timing (50TPS)".to_string(), state.timing_mode == TimingMode::_50Hz));
self.option_menu.push_entry(MenuEntry::Toggle("Lighting effects".to_string(), state.settings.shader_effects));
if state.constants.supports_og_textures {
self.option_menu.push_entry(MenuEntry::Toggle("Original textures".to_string(), state.settings.original_textures));
} else {
self.option_menu.push_entry(MenuEntry::Disabled("Original textures".to_string()));
}
if state.constants.is_cs_plus {
self.option_menu.push_entry(MenuEntry::Toggle("Seasonal textures".to_string(), state.settings.seasonal_textures));
} else {
self.option_menu.push_entry(MenuEntry::Disabled("Seasonal textures".to_string()));
}
self.option_menu.push_entry(MenuEntry::Active("Join our Discord".to_string()));
self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned()));
self.option_menu.push_entry(MenuEntry::Active("Back".to_string()));
self.option_menu.height = self.option_menu.entries.len() as u16 * 14 + 6;
Ok(())
}
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
self.controller.update(state, ctx)?;
self.main_menu.x = ((state.canvas_size.0 - self.main_menu.width as f32) / 2.0).floor() as isize;
self.main_menu.y = ((state.canvas_size.1 + 70.0 - self.main_menu.height as f32) / 2.0).floor() as isize;
@ -119,7 +129,7 @@ impl Scene for TitleScene {
match self.current_menu {
CurrentMenu::MainMenu => {
match self.main_menu.tick(state) {
match self.main_menu.tick(&mut self.controller, state) {
MenuSelectionResult::Selected(0, _) => {
state.reset();
state.sound_manager.play_song(0, &state.constants, ctx)?;
@ -141,7 +151,7 @@ impl Scene for TitleScene {
}
}
CurrentMenu::OptionMenu => {
match self.option_menu.tick(state) {
match self.option_menu.tick(&mut self.controller, state) {
MenuSelectionResult::Selected(0, toggle) => {
if let MenuEntry::Toggle(_, value) = toggle {
match state.timing_mode {

102
src/settings.rs Normal file
View file

@ -0,0 +1,102 @@
use ggez::{Context, GameResult};
use serde::{Deserialize, Serialize};
use winit::event::VirtualKeyCode;
use crate::input::keyboard_player_controller::KeyboardController;
use crate::input::player_controller::PlayerController;
use crate::player::TargetPlayer;
#[derive(Serialize, Deserialize)]
pub struct Settings {
pub seasonal_textures: bool,
pub original_textures: bool,
pub shader_effects: bool,
pub motion_interpolation: bool,
pub touch_controls: bool,
pub player1_key_map: PlayerKeyMap,
pub player2_key_map: PlayerKeyMap,
#[serde(skip)]
pub speed: f64,
#[serde(skip)]
pub god_mode: bool,
#[serde(skip)]
pub infinite_booster: bool,
#[serde(skip)]
pub debug_outlines: bool,
}
impl Settings {
pub fn load(_ctx: &mut Context) -> GameResult<Settings> {
Ok(Settings::default())
}
pub fn create_player1_controller(&self) -> Box<dyn PlayerController> {
Box::new(KeyboardController::new(TargetPlayer::Player1))
}
pub fn create_player2_controller(&self) -> Box<dyn PlayerController> {
Box::new(KeyboardController::new(TargetPlayer::Player2))
}
}
impl Default for Settings {
fn default() -> Self {
Settings {
seasonal_textures: true,
original_textures: false,
shader_effects: true,
motion_interpolation: true,
touch_controls: cfg!(target_os = "android"),
player1_key_map: p1_default_keymap(),
player2_key_map: p2_default_keymap(),
speed: 1.0,
god_mode: false,
infinite_booster: false,
debug_outlines: false,
}
}
}
#[derive(Serialize, Deserialize)]
pub struct PlayerKeyMap {
pub left: VirtualKeyCode,
pub up: VirtualKeyCode,
pub right: VirtualKeyCode,
pub down: VirtualKeyCode,
pub prev_weapon: VirtualKeyCode,
pub next_weapon: VirtualKeyCode,
pub jump: VirtualKeyCode,
pub shoot: VirtualKeyCode,
pub inventory: VirtualKeyCode,
pub map: VirtualKeyCode,
}
fn p1_default_keymap() -> PlayerKeyMap {
PlayerKeyMap {
left: VirtualKeyCode::Left,
up: VirtualKeyCode::Up,
right: VirtualKeyCode::Right,
down: VirtualKeyCode::Down,
prev_weapon: VirtualKeyCode::A,
next_weapon: VirtualKeyCode::S,
jump: VirtualKeyCode::Z,
shoot: VirtualKeyCode::X,
inventory: VirtualKeyCode::Q,
map: VirtualKeyCode::W,
}
}
fn p2_default_keymap() -> PlayerKeyMap {
PlayerKeyMap {
left: VirtualKeyCode::Comma,
up: VirtualKeyCode::L,
right: VirtualKeyCode::Slash,
down: VirtualKeyCode::Period,
prev_weapon: VirtualKeyCode::G,
next_weapon: VirtualKeyCode::H,
jump: VirtualKeyCode::B,
shoot: VirtualKeyCode::N,
inventory: VirtualKeyCode::T,
map: VirtualKeyCode::Y,
}
}

36
src/shaders.rs Normal file
View file

@ -0,0 +1,36 @@
use gfx::{self, *};
use ggez::graphics::Shader;
use ggez::{Context, GameResult};
gfx_defines! {
constant WaterShaderParams {
resolution: [f32; 2] = "u_Resolution",
t: f32 = "u_Tick",
}
}
pub struct Shaders {
pub water_shader: Shader<WaterShaderParams>,
pub water_shader_params: WaterShaderParams,
}
impl Shaders {
pub fn new(ctx: &mut Context) -> GameResult<Shaders> {
let water_shader_params = WaterShaderParams {
t: 0.0,
resolution: [0.0, 0.0],
};
Ok(Shaders {
water_shader: Shader::new(
ctx,
"/builtin/shaders/basic_150.vert.glsl",
"/builtin/shaders/water_150.frag.glsl",
water_shader_params,
"WaterShaderParams",
None,
)?,
water_shader_params,
})
}
}

View file

@ -3,27 +3,28 @@ use std::time::Instant;
use bitvec::vec::BitVec;
use chrono::{Datelike, Local};
use gfx::{self, *};
use ggez::{Context, filesystem, GameResult, graphics};
use ggez::filesystem::OpenOptions;
use ggez::graphics::{Canvas, Shader};
use ggez::graphics::Canvas;
use num_traits::clamp;
use crate::bmfont_renderer::BMFontRenderer;
use crate::caret::{Caret, CaretType};
use crate::common::{ControlFlags, Direction, FadeState, KeyState};
use crate::common::{ControlFlags, Direction, FadeState};
use crate::engine_constants::EngineConstants;
use crate::input::touch_controls::TouchControls;
use crate::npc::{NPC, NPCTable};
use crate::profile::GameProfile;
use crate::rng::RNG;
use crate::scene::game_scene::GameScene;
use crate::scene::Scene;
use crate::settings::Settings;
use crate::shaders::Shaders;
use crate::sound::SoundManager;
use crate::stage::StageData;
use crate::str;
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::texture_set::{TextureSet, g_mag};
use crate::touch_controls::TouchControls;
use crate::texture_set::{g_mag, TextureSet};
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum TimingMode {
@ -80,51 +81,6 @@ impl Season {
}
}
pub struct Settings {
pub god_mode: bool,
pub infinite_booster: bool,
pub speed: f64,
pub seasonal_textures: bool,
pub original_textures: bool,
pub shader_effects: bool,
pub motion_interpolation: bool,
pub debug_outlines: bool,
pub touch_controls: bool,
}
gfx_defines! {
constant WaterShaderParams {
resolution: [f32; 2] = "u_Resolution",
t: f32 = "u_Tick",
}
}
pub struct Shaders {
pub water_shader: Shader<WaterShaderParams>,
pub water_shader_params: WaterShaderParams,
}
impl Shaders {
pub fn new(ctx: &mut Context) -> GameResult<Shaders> {
let water_shader_params = WaterShaderParams {
t: 0.0,
resolution: [0.0, 0.0],
};
Ok(Shaders {
water_shader: Shader::new(
ctx,
"/builtin/shaders/basic_150.vert.glsl",
"/builtin/shaders/water_150.frag.glsl",
water_shader_params,
"WaterShaderParams",
None,
)?,
water_shader_params,
})
}
}
pub struct SharedGameState {
pub timing_mode: TimingMode,
pub control_flags: ControlFlags,
@ -137,8 +93,6 @@ pub struct SharedGameState {
pub quake_counter: u16,
pub teleporter_slots: Vec<(u16, u16)>,
pub carets: Vec<Caret>,
pub key_state: KeyState,
pub key_trigger: KeyState,
pub touch_controls: TouchControls,
pub base_path: String,
pub npc_table: NPCTable,
@ -162,7 +116,6 @@ pub struct SharedGameState {
pub sound_manager: SoundManager,
pub settings: Settings,
pub shutdown: bool,
key_old: u16,
}
impl SharedGameState {
@ -175,7 +128,7 @@ impl SharedGameState {
let mut constants = EngineConstants::defaults();
let mut base_path = "/";
let settings = SharedGameState::load_settings(ctx)?;
let settings = Settings::load(ctx)?;
if filesystem::exists(ctx, "/base/Nicalis.bmp") {
info!("Cave Story+ (PC) data files detected.");
@ -213,8 +166,6 @@ impl SharedGameState {
quake_counter: 0,
teleporter_slots: Vec::with_capacity(8),
carets: Vec::with_capacity(32),
key_state: KeyState(0),
key_trigger: KeyState(0),
touch_controls: TouchControls::new(),
base_path: str!(base_path),
npc_table: NPCTable::new(),
@ -238,21 +189,6 @@ impl SharedGameState {
sound_manager: SoundManager::new(ctx)?,
settings,
shutdown: false,
key_old: 0,
})
}
fn load_settings(ctx: &mut Context) -> GameResult<Settings> {
Ok(Settings {
god_mode: false,
infinite_booster: false,
speed: 1.0,
seasonal_textures: true,
original_textures: false,
shader_effects: true,
motion_interpolation: true,
debug_outlines: false,
touch_controls: cfg!(target_os = "android"),
})
}
@ -268,8 +204,8 @@ impl SharedGameState {
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {
let mut next_scene = GameScene::new(self, ctx, 13)?;
next_scene.player.x = 10 * 16 * 0x200;
next_scene.player.y = 8 * 16 * 0x200;
next_scene.player1.x = 10 * 16 * 0x200;
next_scene.player1.y = 8 * 16 * 0x200;
self.fade_state = FadeState::Hidden;
self.textscript_vm.state = TextScriptExecutionState::Running(200, 0);
@ -280,9 +216,9 @@ impl SharedGameState {
pub fn start_intro(&mut self, ctx: &mut Context) -> GameResult {
let mut next_scene = GameScene::new(self, ctx, 72)?;
next_scene.player.cond.set_hidden(true);
next_scene.player.x = 3 * 16 * 0x200;
next_scene.player.y = 3 * 16 * 0x200;
next_scene.player1.cond.set_hidden(true);
next_scene.player1.x = 3 * 16 * 0x200;
next_scene.player1.y = 3 * 16 * 0x200;
next_scene.intro_mode = true;
self.fade_state = FadeState::Hidden;
self.textscript_vm.state = TextScriptExecutionState::Running(100, 0);
@ -334,9 +270,6 @@ impl SharedGameState {
self.teleporter_slots.clear();
self.quake_counter = 0;
self.carets.clear();
self.key_state.0 = 0;
self.key_trigger.0 = 0;
self.key_old = 0;
self.new_npcs.clear();
self.textscript_vm.set_mode(ScriptMode::Map);
self.textscript_vm.suspend = true;
@ -353,13 +286,6 @@ impl SharedGameState {
Ok(())
}
pub fn update_key_trigger(&mut self) {
let mut trigger = self.key_state.0 ^ self.key_old;
trigger &= self.key_state.0;
self.key_old = self.key_state.0;
self.key_trigger = KeyState(trigger);
}
pub fn tick_carets(&mut self) {
for caret in self.carets.iter_mut() {
caret.tick(&self.effect_rng, &self.constants);

View file

@ -561,7 +561,10 @@ impl TextScriptVM {
if remaining > 1 {
let ticks = if state.textscript_vm.flags.fast() {
0
} else if state.key_state.jump() || state.key_state.fire() {
} else if game_scene.player1.controller.jump()
|| game_scene.player1.controller.shoot()
|| game_scene.player2.controller.jump()
|| game_scene.player2.controller.shoot() {
1
} else {
4
@ -593,13 +596,16 @@ impl TextScriptVM {
break;
}
if state.key_trigger.left() || state.key_trigger.right() {
if game_scene.player1.controller.trigger_left()
|| game_scene.player1.controller.trigger_right()
|| game_scene.player2.controller.trigger_left()
|| game_scene.player2.controller.trigger_right() {
state.sound_manager.play_sfx(1);
state.textscript_vm.state = TextScriptExecutionState::WaitConfirmation(event, ip, no_event, 0, !selection);
break;
}
if state.key_trigger.jump() {
if game_scene.player1.controller.trigger_jump() || game_scene.player2.controller.trigger_jump() {
state.sound_manager.play_sfx(18);
match selection {
ConfirmSelection::Yes => {
@ -614,13 +620,16 @@ impl TextScriptVM {
break;
}
TextScriptExecutionState::WaitStanding(event, ip) => {
if game_scene.player.flags.hit_bottom_wall() {
if game_scene.player1.flags.hit_bottom_wall() {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
}
break;
}
TextScriptExecutionState::WaitInput(event, ip) => {
if state.key_trigger.jump() || state.key_trigger.fire() {
if game_scene.player1.controller.trigger_jump()
|| game_scene.player1.controller.trigger_shoot()
|| game_scene.player2.controller.trigger_jump()
|| game_scene.player2.controller.trigger_shoot() {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
}
break;
@ -694,7 +703,7 @@ impl TextScriptVM {
state.textscript_vm.flags.set_background_visible(false);
state.textscript_vm.stack.clear();
game_scene.player.cond.set_interacted(false);
game_scene.player1.cond.set_interacted(false);
game_scene.frame.update_target = UpdateTarget::Player;
exec_state = TextScriptExecutionState::Ended;
@ -702,7 +711,7 @@ impl TextScriptVM {
OpCode::SLP => {
state.textscript_vm.set_mode(ScriptMode::StageSelect);
let event_num = if let Some(slot) = state.teleporter_slots.get(game_scene.current_teleport_slot as usize) {
let event_num = if let Some(slot) = state.teleporter_slots.get(game_scene.stage_select.current_teleport_slot as usize) {
1000 + slot.0
} else {
1000
@ -726,7 +735,7 @@ impl TextScriptVM {
state.control_flags.set_tick_world(false);
state.control_flags.set_control_enabled(false);
game_scene.player.shock_counter = 0;
game_scene.player1.shock_counter = 0;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -734,8 +743,8 @@ impl TextScriptVM {
state.control_flags.set_tick_world(true);
state.control_flags.set_control_enabled(false);
game_scene.player.up = false;
game_scene.player.shock_counter = 0;
game_scene.player1.up = false;
game_scene.player1.shock_counter = 0;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -748,7 +757,7 @@ impl TextScriptVM {
OpCode::MYD => {
let new_direction = read_cur_varint(&mut cursor)? as usize;
if let Some(direction) = Direction::from_int(new_direction) {
game_scene.player.direction = direction;
game_scene.player1.direction = direction;
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
@ -756,14 +765,14 @@ impl TextScriptVM {
OpCode::MYB => {
let new_direction = read_cur_varint(&mut cursor)? as usize;
game_scene.player.vel_y = -0x200;
game_scene.player1.vel_y = -0x200;
if let Some(direction) = Direction::from_int_facing(new_direction) {
match direction {
Direction::Left => game_scene.player.vel_x = 0x200,
Direction::Up => game_scene.player.vel_y = -0x200,
Direction::Right => game_scene.player.vel_x = -0x200,
Direction::Bottom => game_scene.player.vel_y = 0x200,
Direction::Left => game_scene.player1.vel_x = 0x200,
Direction::Up => game_scene.player1.vel_y = -0x200,
Direction::Right => game_scene.player1.vel_x = -0x200,
Direction::Bottom => game_scene.player1.vel_y = 0x200,
Direction::FacingPlayer => {
// todo npc direction dependent bump
}
@ -773,12 +782,12 @@ impl TextScriptVM {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::SMC => {
game_scene.player.cond.set_hidden(false);
game_scene.player1.cond.set_hidden(false);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::HMC => {
game_scene.player.cond.set_hidden(true);
game_scene.player1.cond.set_hidden(true);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -823,7 +832,7 @@ impl TextScriptVM {
let item_id = read_cur_varint(&mut cursor)? as u16;
let event_num = read_cur_varint(&mut cursor)? as u16;
if game_scene.inventory.has_item(item_id) {
if game_scene.inventory_player1.has_item(item_id) {
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
@ -834,7 +843,7 @@ impl TextScriptVM {
let amount = read_cur_varint(&mut cursor)? as u16;
let event_num = read_cur_varint(&mut cursor)? as u16;
if game_scene.inventory.has_item_amount(item_id, Ordering::Equal, amount) {
if game_scene.inventory_player1.has_item_amount(item_id, Ordering::Equal, amount) {
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
@ -845,7 +854,7 @@ impl TextScriptVM {
let event_num = read_cur_varint(&mut cursor)? as u16;
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon);
if weapon_type.is_some() && game_scene.inventory.has_weapon(weapon_type.unwrap()) {
if weapon_type.is_some() && game_scene.inventory_player1.has_weapon(weapon_type.unwrap()) {
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
@ -893,7 +902,7 @@ impl TextScriptVM {
}
}
OpCode::MM0 => {
game_scene.player.vel_x = 0;
game_scene.player1.vel_x = 0;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -925,8 +934,8 @@ impl TextScriptVM {
}
OpCode::MLp => {
let life = read_cur_varint(&mut cursor)? as u16;
game_scene.player.life += life;
game_scene.player.max_life += life;
game_scene.player1.life += life;
game_scene.player1.max_life += life;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -999,12 +1008,12 @@ impl TextScriptVM {
let mut new_scene = GameScene::new(state, ctx, map_id)?;
new_scene.intro_mode = game_scene.intro_mode;
new_scene.inventory = game_scene.inventory.clone();
new_scene.player = game_scene.player.clone();
new_scene.player.vel_x = 0;
new_scene.player.vel_y = 0;
new_scene.player.x = pos_x;
new_scene.player.y = pos_y;
new_scene.inventory_player1 = game_scene.inventory_player1.clone();
new_scene.player1 = game_scene.player1.clone();
new_scene.player1.vel_x = 0;
new_scene.player1.vel_y = 0;
new_scene.player1.x = pos_x;
new_scene.player1.y = pos_y;
state.control_flags.set_tick_world(true);
state.textscript_vm.flags.0 = 0;
@ -1024,10 +1033,10 @@ impl TextScriptVM {
let pos_x = read_cur_varint(&mut cursor)? as isize * 16 * 0x200;
let pos_y = read_cur_varint(&mut cursor)? as isize * 16 * 0x200;
game_scene.player.vel_x = 0;
game_scene.player.vel_y = 0;
game_scene.player.x = pos_x;
game_scene.player.y = pos_y;
game_scene.player1.vel_x = 0;
game_scene.player1.vel_y = 0;
game_scene.player1.x = pos_x;
game_scene.player1.y = pos_y;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -1036,7 +1045,7 @@ impl TextScriptVM {
let mode: Option<ControlMode> = FromPrimitive::from_u8(control_mode);
if let Some(mode) = mode {
game_scene.player.control_mode = mode;
game_scene.player1.control_mode = mode;
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
@ -1177,7 +1186,7 @@ impl TextScriptVM {
npc.tsc_direction = tsc_direction as u16;
if direction == Direction::FacingPlayer {
npc.direction = if game_scene.player.x < npc.x {
npc.direction = if game_scene.player1.x < npc.x {
Direction::Right
} else {
Direction::Left
@ -1234,7 +1243,7 @@ impl TextScriptVM {
npc.tsc_direction = tsc_direction as u16;
if direction == Direction::FacingPlayer {
npc.direction = if game_scene.player.x < npc.x {
npc.direction = if game_scene.player1.x < npc.x {
Direction::Right
} else {
Direction::Left
@ -1265,7 +1274,7 @@ impl TextScriptVM {
npc.tsc_direction = tsc_direction as u16;
if direction == Direction::FacingPlayer {
npc.direction = if game_scene.player.x < npc.x {
npc.direction = if game_scene.player1.x < npc.x {
Direction::Right
} else {
Direction::Left
@ -1294,7 +1303,7 @@ impl TextScriptVM {
npc.tsc_direction = tsc_direction as u16;
if direction == Direction::FacingPlayer {
npc.direction = if game_scene.player.x < npc.x {
npc.direction = if game_scene.player1.x < npc.x {
Direction::Right
} else {
Direction::Left
@ -1310,15 +1319,15 @@ impl TextScriptVM {
OpCode::LIp => {
let life = read_cur_varint(&mut cursor)? as u16;
game_scene.player.life = clamp(game_scene.player.life + life, 0, game_scene.player.max_life);
game_scene.player1.life = clamp(game_scene.player1.life + life, 0, game_scene.player1.max_life);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::ITp => {
let item_id = read_cur_varint(&mut cursor)? as u16;
if !game_scene.inventory.has_item(item_id) {
game_scene.inventory.add_item(item_id);
if !game_scene.inventory_player1.has_item(item_id) {
game_scene.inventory_player1.add_item(item_id);
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
@ -1327,8 +1336,8 @@ impl TextScriptVM {
let item_id = read_cur_varint(&mut cursor)? as u16;
let amount = read_cur_varint(&mut cursor)? as u16;
if game_scene.inventory.has_item_amount(item_id, Ordering::Less, amount) {
game_scene.inventory.add_item(item_id);
if game_scene.inventory_player1.has_item_amount(item_id, Ordering::Less, amount) {
game_scene.inventory_player1.add_item(item_id);
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
@ -1336,7 +1345,7 @@ impl TextScriptVM {
OpCode::ITm => {
let item_id = read_cur_varint(&mut cursor)? as u16;
game_scene.inventory.consume_item(item_id);
game_scene.inventory_player1.consume_item(item_id);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -1346,7 +1355,7 @@ impl TextScriptVM {
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon_id);
if let Some(wtype) = weapon_type {
game_scene.inventory.add_weapon(wtype, max_ammo);
game_scene.inventory_player1.add_weapon(wtype, max_ammo);
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
@ -1356,32 +1365,32 @@ impl TextScriptVM {
let weapon_type: Option<WeaponType> = FromPrimitive::from_u8(weapon_id);
if let Some(wtype) = weapon_type {
game_scene.inventory.remove_weapon(wtype);
game_scene.inventory_player1.remove_weapon(wtype);
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::AEp => {
game_scene.inventory.refill_all_ammo();
game_scene.inventory_player1.refill_all_ammo();
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::ZAM => {
game_scene.inventory.reset_all_weapon_xp();
game_scene.inventory_player1.reset_all_weapon_xp();
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::EQp => {
let mask = read_cur_varint(&mut cursor)? as u16;
game_scene.player.equip.0 |= mask;
game_scene.player1.equip.0 |= mask;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::EQm => {
let mask = read_cur_varint(&mut cursor)? as u16;
game_scene.player.equip.0 &= !mask;
game_scene.player1.equip.0 &= !mask;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -1455,7 +1464,7 @@ impl TextScriptVM {
if tick_npc != 0 {
if let Some(npc) = game_scene.npc_map.npcs.get(&tick_npc) {
npc.borrow_mut().tick(state, (&mut game_scene.player, &game_scene.npc_map.npcs, &mut game_scene.stage))?;
npc.borrow_mut().tick(state, (&mut game_scene.player1, &game_scene.npc_map.npcs, &mut game_scene.stage))?;
}
}

View file

@ -4,7 +4,7 @@ use std::io::{BufReader, Read, Seek, SeekFrom};
use ggez;
use ggez::{Context, GameError, GameResult, graphics};
use ggez::filesystem;
use ggez::graphics::{Color, Drawable, DrawMode, DrawParam, FilterMode, Image, Mesh, Rect, mint};
use ggez::graphics::{Color, Drawable, DrawMode, DrawParam, FilterMode, Image, Mesh, mint, Rect};
use ggez::graphics::spritebatch::SpriteBatch;
use ggez::nalgebra::{Point2, Vector2};
use image::RgbaImage;
@ -14,7 +14,8 @@ use log::info;
use crate::common;
use crate::common::FILE_TYPES;
use crate::engine_constants::EngineConstants;
use crate::shared_game_state::{Season, Settings};
use crate::settings::Settings;
use crate::shared_game_state::Season;
use crate::str;
pub static mut g_mag: f32 = 1.0;

View file

@ -85,7 +85,7 @@ impl Weapon {
}
fn shoot_bullet_snake(&mut self, player: &Player, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
if state.key_trigger.fire() && bullet_manager.count_bullets_multi([1, 2, 3]) < 4 {
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([1, 2, 3]) < 4 {
let btype = match self.level {
WeaponLevel::Level1 => { 1 }
WeaponLevel::Level2 => { 2 }
@ -141,7 +141,7 @@ impl Weapon {
}
fn shoot_bullet_polar_star(&mut self, player: &Player, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
if state.key_trigger.fire() && bullet_manager.count_bullets_multi([4, 5, 6]) < 2 {
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([4, 5, 6]) < 2 {
let btype = match self.level {
WeaponLevel::Level1 => { 4 }
WeaponLevel::Level2 => { 5 }
@ -203,7 +203,7 @@ impl Weapon {
fn shoot_bullet_fireball(&mut self, player: &Player, bullet_manager: &mut BulletManager, state: &mut SharedGameState) {
let max_bullets = self.level as usize + 1;
if state.key_trigger.fire() && bullet_manager.count_bullets_multi([7, 8, 9]) < max_bullets {
if player.controller.trigger_shoot() && bullet_manager.count_bullets_multi([7, 8, 9]) < max_bullets {
let btype = match self.level {
WeaponLevel::Level1 => { 7 }
WeaponLevel::Level2 => { 8 }