diff --git a/README.md b/README.md index 73bd9cf..8915e9e 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,12 @@ The [rules](./rules/Deep_Future_Rules_1_6.pdf) include tables and guidelines for ## How to Play +### Play Online + +Click the "Play Online" button above to play the game in your browser. + +### Play Locally + - Download [soluna](https://github.com/cloudwu/soluna/releases/tag/nightly) (the game engine runtime). - Clone the game repository. - (Optional) For Linux users with Simplified Chinese, install the font `WenQuanYi Micro Hei`. @@ -40,6 +46,7 @@ soluna.exe main.game Feedback is welcome. Please include your save file when reporting issues. The save file can be found at: - Windows: `%USERPROFILE%\Documents\My Games\deepfuture\save.txt` -- macOS | Linux: `~/.local/share/deepfuture/save.txt` +- macOS & Linux: `~/.local/share/deepfuture/save.txt` +- Web: Browser Local Storage To share your thoughts about the game, please use Discussions. diff --git a/README.zh-CN.md b/README.zh-CN.md index b2f0fd7..f9c155b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -22,6 +22,12 @@ ## 如何游玩 +### 在线体验 + +点击上方“在线体验”按钮,即可在浏览器中游玩游戏。 + +### 本地体验 + - 下载 [soluna](https://github.com/cloudwu/soluna/releases/tag/nightly)(游戏引擎运行时框架)。 - 克隆本游戏仓库。 - (可选)Linux 用户如需简体中文,请安装字体 `WenQuanYi Micro Hei`。 @@ -40,6 +46,7 @@ soluna.exe main.game 游戏正在持续开发与改进中,欢迎参与测试并提供反馈。遇到问题请带上存档文件提交 issue,存档文件可在以下位置找到: - Windows: `%USERPROFILE%\Documents\My Games\deepfuture\save.txt` -- macOS | Linux: `~/.local/share/deepfuture/save.txt` +- macOS & Linux: `~/.local/share/deepfuture/save.txt` +- Web: 浏览器 Local Storage 关于游戏内容的交流,请在 Discussions 中发帖。 diff --git a/core/mouse.lua b/core/mouse.lua index 007786d..65b34d4 100644 --- a/core/mouse.lua +++ b/core/mouse.lua @@ -143,6 +143,10 @@ function mouse.focus_region() return focus.region end +function mouse.focus_object() + return focus.object +end + function mouse.scroll(delta) mouse.z = mouse.z + delta end diff --git a/core/touch.lua b/core/touch.lua new file mode 100644 index 0000000..88ceb05 --- /dev/null +++ b/core/touch.lua @@ -0,0 +1,216 @@ +local mouse = require "core.mouse" + +local TOUCH_LONG_PRESS_FRAMES = 18 +local TOUCH_MOVE_THRESHOLD2 = 12 * 12 +-- Regions that require a confirmation tap. +local DOUBLE_TAP_REGIONS = { + hand = true, + neutral = true, + homeworld = true, + colony = true, + discard = true, + deck = true, + card = true, +} + +local REGION_CONFIRM_DOUBLE = { + deck = true, +} + +local touch = {} + +local current_frame = 0 +local pending_clear_focus + +local function dist2(x1, y1, x2, y2) + local dx = x1 - x2 + local dy = y1 - y2 + return dx * dx + dy * dy +end + +local state = { + active = false, + pressing = false, + double_candidate = false, + require_double = false, + moved = false, + start_frame = 0, + start_x = 0, + start_y = 0, + x = 0, + y = 0, +} + +local last_tap = { + require_double = false, + object = nil, + region = nil, + x = 0, + y = 0, +} + +local function clear_last_tap() + last_tap.require_double = false + last_tap.object = nil + last_tap.region = nil + last_tap.x = 0 + last_tap.y = 0 +end + +local function store_last_tap(focus_object, region, x, y) + if focus_object then + last_tap.require_double = true + last_tap.object = focus_object + last_tap.region = region + last_tap.x = x + last_tap.y = y + return true + end + if region and REGION_CONFIRM_DOUBLE[region] then + last_tap.require_double = true + last_tap.object = nil + last_tap.region = region + last_tap.x = x + last_tap.y = y + return true + end + clear_last_tap() +end + +local function reset_state() + state.active = false + state.pressing = false + state.double_candidate = false + state.require_double = false + state.moved = false + state.start_frame = 0 + state.start_x = 0 + state.start_y = 0 + state.x = 0 + state.y = 0 +end + +local function apply_press() + if state.pressing then + return + end + mouse.mouse_button("left", true) + state.pressing = true + state.double_candidate = false + clear_last_tap() +end + +function touch.begin(x, y) + mouse.mouse_move(x, y) + state.active = true + state.pressing = false + state.moved = false + state.start_frame = current_frame + state.start_x = x + state.start_y = y + state.x = x + state.y = y + state.double_candidate = false + state.require_double = false + local region = mouse.focus_region() + if region and DOUBLE_TAP_REGIONS[region] then + state.require_double = true + end + if last_tap.require_double then + local focus_object = mouse.focus_object() + local same_object = focus_object and focus_object == last_tap.object + local same_region = (focus_object == nil and last_tap.object == nil and region ~= nil and REGION_CONFIRM_DOUBLE[region] and region == last_tap.region) + if same_object or same_region then + state.double_candidate = true + else + clear_last_tap() + end + end +end + +function touch.moved(x, y) + mouse.mouse_move(x, y) + state.x = x + state.y = y + if not state.active then + return + end + if not state.moved and dist2(x, y, state.start_x, state.start_y) > TOUCH_MOVE_THRESHOLD2 then + state.moved = true + state.double_candidate = false + end +end + +function touch.ended(x, y) + mouse.mouse_move(x, y) + state.x = x + state.y = y + local region = mouse.focus_region() + if state.pressing then + mouse.mouse_button("left", false) + clear_last_tap() + reset_state() + return + end + if state.active then + if not state.moved then + if state.require_double then + if state.double_candidate then + mouse.mouse_button("left", true) + mouse.mouse_button("left", false) + clear_last_tap() + pending_clear_focus = true + else + local focus_object = mouse.focus_object() + if not store_last_tap(focus_object, region, state.x, state.y) then + reset_state() + return + end + end + else + mouse.mouse_button("left", true) + mouse.mouse_button("left", false) + clear_last_tap() + end + else + clear_last_tap() + end + end + reset_state() +end + +function touch.update(frame) + current_frame = frame + if pending_clear_focus then + mouse.set_focus(nil, nil) + pending_clear_focus = nil + end + if not state.active then + return + end + local region = mouse.focus_region() + local need_double = region and DOUBLE_TAP_REGIONS[region] or false + state.require_double = need_double + if need_double then + local candidate = false + if last_tap.require_double then + local focus_object = mouse.focus_object() + if focus_object and focus_object == last_tap.object then + candidate = true + elseif focus_object == nil and last_tap.object == nil and REGION_CONFIRM_DOUBLE[region] and region == last_tap.region then + candidate = true + end + end + state.double_candidate = candidate + else + state.double_candidate = false + end + if state.pressing or state.moved then + return + end + if frame - state.start_frame >= TOUCH_LONG_PRESS_FRAMES then + apply_press() + end +end + +return touch diff --git a/main.lua b/main.lua index 5d4dcd1..4f56cf1 100644 --- a/main.lua +++ b/main.lua @@ -15,6 +15,7 @@ local loadsave = require "core.loadsave" local track = require "gameplay.track" local vbutton = require "visual.button" local mouse = require "core.mouse" +local touch = require "core.touch" local keyboard = require "core.keyboard" local text = require "soluna.text" local setting =require "core.setting" @@ -155,10 +156,23 @@ function callback.mouse_scroll(x, y) mouse.scroll(x) end +function callback.touch_begin(x, y) + touch.begin(x, y) +end + +function callback.touch_end(x, y) + touch.ended(x, y) +end + +function callback.touch_moved(x, y) + touch.moved(x, y) +end + function callback.frame(count) local x, y = mouse.sync(count) vdesktop.set_mouse(x, y) flow.update() + touch.update(count) -- todo : don't flush card here vdesktop.card_count(card.count "draw", card.count "discard", card.seen()) map.update() diff --git a/visual/region.lua b/visual/region.lua index 3cf2dd2..a709f4b 100644 --- a/visual/region.lua +++ b/visual/region.lua @@ -190,7 +190,14 @@ local function draw_card(obj) end local function test_card(obj, mx, my) - return obj._move == nil and vcard.test(mx, my, obj.x, obj.y, obj.scale) + if obj._move then + return + end + local x, y, scale = obj.x, obj.y, obj.scale + if obj._focus_time then + x, y, scale = focus_args(obj) + end + return vcard.test(mx, my, x, y, scale) end function region:transfer(card, new_region)