Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4945000
feat(spectator): 観戦者が任意のタイミングで途中参加できるようにする
claude Sep 23, 2026
d329696
refactor(spectator): レビュー指摘を反映して可読性を改善
claude Sep 24, 2026
f8b7908
refactor(spectator): 設計レビューを反映して整理
claude Sep 24, 2026
7491044
feat(spectator): 接続時点の状態を送る方式に変更し、観戦用プロトコルを分ける
claude Sep 24, 2026
95f9cd2
feat(spectator): 試合開始時の状態に合わせて観戦を始める方式に変更
claude Sep 24, 2026
0fe6dbd
fix(spectator): 再レビューの指摘を反映
claude Sep 24, 2026
197c120
feat(spectator): 試合中に接続した観戦者は試合の最初から早送りで追いかける
claude Sep 24, 2026
fd0cf94
refactor(spectator): 次の試合の InitGame をバッファの先頭に戻して保持する
claude Sep 24, 2026
e7ef63c
chore(spectator): カードの選択状態の保持場所を調べる一時的な診断ログを追加
claude Sep 27, 2026
718716e
Revert "chore(spectator): カードの選択状態の保持場所を調べる一時的な診断ログを追加"
claude Sep 27, 2026
85e0399
fix(spectator): カードを入力で選ばせて同期する
claude Sep 27, 2026
29947ec
fix(spectator): カードの切り替えが受け付けられたことを確認しながら入力する
claude Sep 27, 2026
e6374f3
fix(spectator): ホストが送るカードを vs_mode の値にする
claude Sep 27, 2026
ed85689
chore(spectator): カードの値の場所を調べる診断ログを再度追加
claude Sep 28, 2026
d62918d
fix(junowen-lib): Player のカードの位置を +94h に直す
claude Sep 28, 2026
b40fe10
fix(spectator): 試合中に観戦を始めたときに早送りで追いかける
claude Sep 28, 2026
ed432d8
chore(spectator): 早送りが効かない原因を調べる一時的な診断ログを追加
claude Sep 28, 2026
56fc4c2
feat(spectator): 早送り中は画面の表示を間引いて追いつく
claude Sep 28, 2026
a87f850
refactor(junowen-lib): PeerConnection の protocol を引数で受け取る形に変更
progre Oct 8, 2026
2c34946
chore(spectator): 早送りの原因調査用の診断ログを削除
progre Oct 8, 2026
50a2987
refactor(spectator): present_skipper のグローバル変数を観戦セッションの状態へ移動
progre Oct 9, 2026
f93e69a
refactor(workspace): クレートを crates/app と crates/features へ移動
progre Oct 11, 2026
5d6ac3e
refactor(junowen): custom_direct_3d を junowen-direct3d クレートへ切り出し
progre Oct 11, 2026
4f5ee64
refactor(junowen): メニュー部品を junowen-menu クレートへ切り出し
progre Oct 11, 2026
0643b88
refactor(junowen-lobby): junowen-menu を junowen-lobby に改名し、ロビーのシーンとメニ…
progre Oct 11, 2026
083a70c
refactor(junowen-lobby): 行単位の描画とテキスト配置を MenuRenderer 経由にして junowen-lo…
progre Oct 11, 2026
fe83339
docs(junowen-lobby): クレートとテキスト配置の説明を読みやすく修正
progre Oct 11, 2026
3633b88
docs(junowen-lobby): クレートの説明でトレイトの役割を明確化
progre Oct 11, 2026
748dc12
feat(spectator): 最初の観戦者を観戦ホストにして複数の観戦者を中継する
progre Oct 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 12 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ members = [
"archives/th19replayrecorder",
"archives/th19savesettingsseparately",
"archives/th19seed",
"junowen",
"junowen-lib",
"junowen-server",
"th19loader",
"crates/app/junowen",
"crates/app/junowen-server",
"crates/app/th19loader",
"crates/features/junowen-direct3d",
"crates/features/junowen-lib",
"crates/features/junowen-lobby",
]
default-members = ["junowen"]
default-members = ["crates/app/junowen"]
resolver = "2"

[workspace.package]
Expand All @@ -26,7 +28,11 @@ anyhow = { version = "1.0.95", features = ["backtrace"] }
async-trait = "0.1.85"
bytes = "1.9.0"
clipboard-win = "5.4.0"
junowen-lib = { path = "./junowen-lib" }
derive-new = "0.6.0"
getset = "0.1.2"
junowen-direct3d = { path = "./crates/features/junowen-direct3d" }
junowen-lib = { path = "./crates/features/junowen-lib" }
junowen-lobby = { path = "./crates/features/junowen-lobby" }
rmp-serde = "1.3.0"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.135"
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 4 additions & 2 deletions junowen/Cargo.toml → crates/app/junowen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ anyhow.workspace = true
async-trait.workspace = true
bytes.workspace = true
clipboard-win.workspace = true
derive-new = "0.6.0"
getset = "0.1.2"
derive-new.workspace = true
getset.workspace = true
junowen-direct3d.workspace = true
junowen-lib.workspace = true
junowen-lobby.workspace = true
reqwest = { version = "0.12.7", features = ["json"] }
rmp-serde.workspace = true
rust-i18n = "3.1.2"
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions junowen/src/helper.rs → crates/app/junowen/src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ pub fn pushed_f1(input_devices: &InputDevices) -> bool {
let raw_keys = input_devices.keyboard_input().raw_keys();
raw_keys[0x70] & 0x80 != 0
}

pub fn pushed_escape(input_devices: &InputDevices) -> bool {
let raw_keys = input_devices.keyboard_input().raw_keys();
raw_keys[0x1b] & 0x80 != 0
}
6 changes: 5 additions & 1 deletion junowen/src/junowen.rs → crates/app/junowen/src/junowen.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::ffi::c_void;

use junowen_direct3d::Direct3DDeviceEventListener;
use junowen_lib::{
Th19, Th19EventListener,
structs::{app::ScreenId, others::RenderingText},
};
use windows::Win32::Graphics::Direct3D9::IDirect3DDevice9;

use crate::{
custom_direct_3d::Direct3DDeviceEventListener,
file::{Features, SettingsRepo},
lobby::{self, Lobby, TitleMenuModifier},
state::State,
Expand Down Expand Up @@ -90,6 +90,10 @@ impl Th19EventListener for Junowen {
}

impl Direct3DDeviceEventListener for &Junowen {
fn should_skip_present(&self) -> bool {
self.state.should_skip_present()
}

fn on_before_present(&self, device: &IDirect3DDevice9) {
if !self.title_menu_modifier.selected_junowen() {
return;
Expand Down
4 changes: 2 additions & 2 deletions junowen/src/lib.rs → crates/app/junowen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
mod custom_direct_3d;
mod file;
mod helper;
mod junowen;
mod lobby;
mod present_skipper;
mod session;
mod signaling;
mod state;
Expand All @@ -12,7 +12,7 @@ rust_i18n::i18n!("locales", fallback = "en");

use std::{cell::OnceCell, mem::take, path::Path, ptr::null_mut, slice, sync::LazyLock};

use custom_direct_3d::CustomDirect3D9;
use junowen_direct3d::CustomDirect3D9;
use junowen_lib::{
Th19, Th19EventDispatcher,
hook_utils::{WELL_KNOWN_VERSION_HASHES, calc_th19_hash, show_warn_dialog},
Expand Down
16 changes: 7 additions & 9 deletions junowen/src/lobby.rs → crates/app/junowen/src/lobby.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
mod common_menu;
mod helper;
mod overlay;
mod pure_p2p_guest;
mod pure_p2p_offerer;
mod pure_p2p_scenes;
mod room;
mod root;
mod signaling_code;
mod text_layout;
mod title_menu_modifier;

use std::ffi::c_void;

use getset::{Getters, MutGetters};
use junowen_lib::{Th19, structs::input_devices::InputValue};
use junowen_lobby::overlay_description;

use crate::{
file::SettingsRepo,
Expand All @@ -24,8 +23,7 @@ use crate::{
};

use self::{
common_menu::LobbyScene,
helper::overlay_description,
common_menu::{LobbyScene, MenuButtons, to_menu_buttons},
pure_p2p_scenes::PureP2pScenes,
room::{reserved::ReservedRoom, shared::SharedRoom, tcp_signaling::TcpSignaling},
root::Root,
Expand All @@ -44,7 +42,7 @@ pub struct Lobby {
reserved_room: ReservedRoom,
tcp_signaling: TcpSignaling,
pure_p2p: PureP2pScenes,
prev_input: InputValue,
prev_input: MenuButtons,
#[getset(get = "pub", get_mut = "pub")]
waiting_for_match: Option<WaitingForMatch>,
}
Expand All @@ -61,17 +59,17 @@ impl Lobby {
reserved_room: ReservedRoom::new(),
tcp_signaling: TcpSignaling::new(),
pure_p2p: PureP2pScenes::default(),
prev_input: InputValue::full(),
prev_input: to_menu_buttons(InputValue::full()),
}
}

pub fn clear_input(&mut self) {
self.prev_input = InputValue::full();
self.prev_input = to_menu_buttons(InputValue::full());
}

pub fn on_input_menu(&mut self, th19: &mut Th19) {
self.prev_scene = self.scene;
let current_input = th19.menu_input().current();
let current_input = to_menu_buttons(th19.menu_input().current());
th19.menu_input_mut().set_current(InputValue::empty());

if let Some(scene) = match self.scene {
Expand Down Expand Up @@ -134,7 +132,7 @@ impl Lobby {
),
} {
self.scene = scene;
self.prev_input = InputValue::full();
self.prev_input = to_menu_buttons(InputValue::full());
} else {
self.prev_input = current_input;
}
Expand Down
68 changes: 68 additions & 0 deletions crates/app/junowen/src/lobby/common_menu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::ffi::c_void;

use junowen_lib::{
Th19,
structs::{
input_devices::{InputFlags, InputValue},
others::RenderingText,
},
};
use junowen_lobby::{MenuDevice, MenuRenderer, MenuSound, MenuText};

pub use junowen_lobby::{
CommonMenu, LobbyScene, Menu, MenuButtons, MenuItem, OnMenuInputResult, menu_item_color,
render_label_value,
};

pub fn to_menu_buttons(input: InputValue) -> MenuButtons {
let bits = |flags: &[InputFlags]| {
flags
.iter()
.enumerate()
.filter(|&(_, &flag)| input.0.contains(flag))
.fold(0u8, |acc, (i, _)| acc | (1 << i))
};
MenuButtons {
up: input.0.contains(InputFlags::UP),
down: input.0.contains(InputFlags::DOWN),
decide: bits(&[InputFlags::SHOT, InputFlags::ENTER]),
cancel: bits(&[InputFlags::CHARGE, InputFlags::BOMB, InputFlags::PAUSE]),
}
}

pub struct Th19MenuDevice<'a>(pub &'a Th19);

impl MenuDevice for Th19MenuDevice<'_> {
fn play_sound(&self, sound: MenuSound) {
let id = match sound {
MenuSound::Cancel => 0x09,
MenuSound::Decide => 0x07,
MenuSound::Select => 0x0a,
};
self.0.play_sound(self.0.sound_manager(), id, 0);
}

fn keyboard_raw_keys(&self) -> &[u8; 256] {
self.0.input_devices().keyboard_input().raw_keys()
}
}

pub struct Th19MenuRenderer<'a> {
pub th19: &'a Th19,
pub text_renderer: &'a c_void,
}

impl MenuRenderer for Th19MenuRenderer<'_> {
fn render_text(&self, text: &MenuText) {
let mut rt = RenderingText::default();
rt.set_text(text.text);
rt.set_x(text.x, self.th19.window_inner());
rt.set_y(text.y, self.th19.window_inner());
rt.color = text.color;
rt.font_type = text.font_type;
rt.drop_shadow = text.drop_shadow;
rt.horizontal_align = text.horizontal_align;
rt.vertical_align = text.vertical_align;
self.th19.render_text(self.text_renderer, &rt);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use windows_core::{HSTRING, w};

use super::Lobby;

pub use junowen_lobby::OverlayText;

/// 縁取りの太さと文字サイズの比
const OUTLINE_RATIO: f64 = 1.0 / 29.0;

Expand Down Expand Up @@ -104,15 +106,6 @@ fn render_text_to_screen(
unsafe { DrawTextW(hdc, &mut text, &mut rect, DT_LEFT) };
}

/// ゲーム内フォントでは英数字しか描画できないため、多言語のテキストは
/// GDI で画面に重ねて描画する。座標はゲーム内と同じ 1280x960 換算。
pub struct OverlayText {
pub left: i32,
pub top: i32,
pub font_size: u32,
pub string: String,
}

pub struct Font {
obj: HGDIOBJ,
size: u32,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@ use std::ffi::c_void;
use clipboard_win::set_clipboard_string;
use junowen_lib::{
Th19,
connection::signaling::{
SignalingCodeType, socket::async_read_write_socket::SignalingServerMessage,
connection::{
BATTLE_PROTOCOL,
signaling::{SignalingCodeType, socket::async_read_write_socket::SignalingServerMessage},
},
structs::input_devices::InputValue,
};
use junowen_lobby::{TextLayout, signaling_code_line_count};
use rust_i18n::t;
use tokio::sync::mpsc;

use crate::session::battle::BattleSession;

use super::{
super::signaling::Signaling,
common_menu::{CommonMenu, LobbyScene, Menu, MenuItem, OnMenuInputResult},
helper::signaling_code_line_count,
common_menu::{
CommonMenu, LobbyScene, Menu, MenuButtons, MenuItem, OnMenuInputResult, Th19MenuDevice,
Th19MenuRenderer,
},
overlay::OverlayText,
signaling_code::paste_signaling_code,
text_layout::TextLayout,
};

pub struct PureP2pGuest {
Expand All @@ -45,7 +47,11 @@ impl PureP2pGuest {
0,
),
),
signaling: Signaling::new(session_tx, |conn, dc| BattleSession::new(conn, dc, false)),
signaling: Signaling::new(
session_tx,
|conn, dc| BattleSession::new(conn, dc, false),
BATTLE_PROTOCOL,
),
session_rx: Some(session_rx),
offer: None,
answer_generated: false,
Expand All @@ -55,8 +61,8 @@ impl PureP2pGuest {

pub fn on_input_menu(
&mut self,
current_input: InputValue,
prev_input: InputValue,
current_input: MenuButtons,
prev_input: MenuButtons,
th19: &Th19,
session_rx: &mut Option<mpsc::Receiver<BattleSession>>,
) -> Option<LobbyScene> {
Expand Down Expand Up @@ -90,7 +96,7 @@ impl PureP2pGuest {
}
match self
.common_menu
.on_input_menu(current_input, prev_input, th19)
.on_input_menu(current_input, prev_input, &Th19MenuDevice(th19))
{
OnMenuInputResult::None => None,
OnMenuInputResult::Cancel => {
Expand Down Expand Up @@ -165,8 +171,12 @@ impl PureP2pGuest {
}

pub fn on_render_texts(&self, th19: &Th19, text_renderer: &c_void) {
self.common_menu.on_render_texts(th19, text_renderer);
self.layout().render_codes(th19, text_renderer);
let renderer = Th19MenuRenderer {
th19,
text_renderer,
};
self.common_menu.on_render_texts(&renderer);
self.layout().render_codes(&renderer);
}

pub fn overlay_texts(&self) -> Vec<OverlayText> {
Expand Down
Loading
Loading