205 Commits
Author SHA1 Message Date
Vick Scarlet 2c615523e3 feature: bili-toy cloud fallback local 2026-08-16 19:52:03 +08:00
Vick Scarlet da997b7a8e update: remove debug log 2026-08-16 18:33:39 +08:00
Vick Scarlet fca4b7b557 update: remove debugger 2026-08-16 18:29:41 +08:00
Vick Scarlet 6c803d4eef update: bili-toy 2026-08-16 18:26:59 +08:00
Vick Scarlet 30b4791813 update: toast achievement icon & style 2026-08-16 14:24:29 +08:00
Vick Scarlet 9017780898 add: toast achievement & message 2026-08-16 13:37:32 +08:00
Vick Scarlet aff50e2521 add: github button 2026-08-16 01:44:24 +08:00
Vick Scarlet 0e8bf1131a update: rename component 2026-08-16 00:54:40 +08:00
Vick Scarlet bc2d89fa52 update: style 2026-08-16 00:45:22 +08:00
Vick Scarlet 63cc529479 update: style 2026-08-16 00:39:41 +08:00
Vick Scarlet 847d72c384 update: thanks 2026-08-16 00:26:00 +08:00
Vick Scarlet ef55eb703a update: style 2026-08-15 22:38:31 +08:00
Vick Scarlet f2e8fac4de update: achievement 2026-08-15 22:11:37 +08:00
Vick Scarlet a2e40230c7 update: character allocation 2026-08-15 18:16:38 +08:00
Vick Scarlet 10debc6515 update: character 2026-08-14 21:00:59 +08:00
Vick Scarlet b8d250906a update: lint config 2026-08-14 16:42:14 +08:00
Vick Scarlet d0f7dbb464 add: character 2026-08-14 00:57:02 +08:00
Vick Scarlet 9c8478c102 add: mode & character page 2026-08-13 16:43:29 +08:00
Vick Scarlet 4be851a591 update: phone style 2026-08-13 16:31:16 +08:00
Vick Scarlet 71daeb3368 update: theme-toggle-btn position 2026-08-12 23:26:11 +08:00
Vick Scarlet 5031f540cb update: dev params 2026-08-12 22:47:41 +08:00
Vick Scarlet 2d35eda931 update: play property animation
update: event format field
fix: test
2026-08-12 21:57:49 +08:00
Vick Scarlet f439141ded feature: lock only one use radio mode 2026-08-12 17:41:51 +08:00
Vick Scarlet 5a4821433a feature: multiple lock, pick min max 2026-08-12 17:30:28 +08:00
Vick Scarlet cd339c2103 update: game core flow pages 2026-08-12 02:38:37 +08:00
Vick Scarlet 5d50a4ea37 feature: spirit allocation 2026-08-11 14:12:17 +08:00
Vick Scarlet 88536c7934 fix: talent 2026-08-11 14:10:52 +08:00
Vick Scarlet 5353edd6a1 update: hooks, web 2026-08-11 02:17:31 +08:00
Vick Scarlet 727fe5de77 update: core support rng 2026-08-03 22:36:02 +08:00
Vick Scarlet d30b93ebf2 update: dependencies 2026-08-03 11:52:55 +08:00
Vick Scarlet 487a728eeb fix: trigger return id error 2026-08-02 22:04:46 +08:00
Vick Scarlet 9df9e8776e add: achievement, game 2026-08-02 22:00:21 +08:00
Vick Scarlet b4177d7657 update: talent, state props 2026-08-02 17:36:04 +08:00
Vick Scarlet f60e279f62 update: state FlatState 2026-07-30 20:29:08 +08:00
Vick Scarlet 72bf21d592 update: state FlatMapper 2026-07-30 20:14:06 +08:00
Vick Scarlet 329a4e3a81 update: state, event, condition, data.event.types 2026-07-30 19:56:12 +08:00
Vick Scarlet 97988a95ba update: data transformers 2026-07-29 17:53:40 +08:00
Vick Scarlet 4043fa887a update: ts 2026-07-29 17:03:26 +08:00
Vick Scarlet 1540a5d142 fix: number check 2026-07-28 14:21:22 +08:00
Vick Scarlet 42d874556c change: document.body theme color 2026-07-27 23:57:22 +08:00
Vick Scarlet 43751ad2db update: 名人模式解锁条件 2026-07-27 22:03:31 +08:00
Vick Scarlet 6fdbcea925 add: bili-toy-sdk cloud save support 2026-07-27 14:32:13 +08:00
Vick Scarlet 50411b82a0 add: channel switch 2026-07-27 14:28:08 +08:00
Vick Scarlet 988fcacd2d update: event 2026-07-27 13:57:30 +08:00
Vick Scarlet 2b62a3a838 remove unused libs from public 2026-07-27 13:57:09 +08:00
Vick Scarlet a10861eed9 test 2026-07-08 20:28:49 +08:00
Vick Scarlet 3f60a7d052 Add codeql-config.yml 2026-07-08 20:11:51 +08:00
Vick Scarlet 3b6eeaeb3a update: dependencies 2026-07-08 20:05:03 +08:00
dependabot[bot] c4e1ea1f0d build(deps-dev): bump vitest from 4.1.9 to 4.1.10
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.9 to 4.1.10.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-08 19:57:15 +08:00
dependabot[bot] e59ab00384 build(deps-dev): bump vite from 8.1.0 to 8.1.3
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.1.0 to 8.1.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.1.3/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-08 19:54:51 +08:00
Vick Scarlet 943fa8da23 update: xlsx blockExoticSubdeps 2026-06-29 15:14:17 +08:00
Vick Scarlet 1eda000ecc update: dependabot.yml 2026-06-29 15:04:35 +08:00
Vick Scarlet 03bcb19942 update: vt 2026-06-29 14:59:00 +08:00
Vick Scarlet d52d2d5d79 update: dependencies 2026-06-29 14:43:49 +08:00
dependabot[bot] 17c4d90bac Bump vite in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 8.0.5 to 8.0.16
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.16/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.16
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-24 12:56:16 +08:00
dependabot[bot] 7963700a37 Bump vite in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 8.0.3 to 8.0.5
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.5
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-24 01:32:05 +08:00
Vick Scarlet f12a147093 fix: format param force to string 2026-03-30 17:49:37 +08:00
dependabot[bot] 59a10587a7 Bump @isaacs/brace-expansion
Bumps the npm_and_yarn group with 1 update in the / directory: @isaacs/brace-expansion.


Updates `@isaacs/brace-expansion` from 5.0.0 to 5.0.1

---
updated-dependencies:
- dependency-name: "@isaacs/brace-expansion"
  dependency-version: 5.0.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-07 21:48:26 +08:00
Vick Scarlet dd5a003801 update: dependencies 2026-01-26 18:21:44 +08:00
Vick Scarlet ddc87ebf40 update: dependencies 2026-01-26 17:50:24 +08:00
dependabot[bot] 23bcae0b74 Bump js-yaml in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [js-yaml](https://github.com/nodeca/js-yaml).


Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 15:14:19 +08:00
dependabot[bot] e075dfc23d Bump vite in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 6.3.6 to 6.4.1
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@6.4.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.4.1
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 15:09:16 +08:00
Vick Scarlet 9088ab0eb1 add: support template prop 2026-01-26 14:47:35 +08:00
dependabot[bot] 39388c6eaf Bump the npm_and_yarn group across 1 directory with 2 updates
Bumps the npm_and_yarn group with 2 updates in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) and [brace-expansion](https://github.com/juliangruber/brace-expansion).


Updates `vite` from 6.2.5 to 6.3.6
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.6/packages/vite)

Updates `brace-expansion` from 2.0.1 to 2.0.2
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v2.0.1...v2.0.2)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.3.6
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: brace-expansion
  dependency-version: 2.0.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-24 17:46:20 +08:00
神戸小鳥 0ce9b0ed02 Add Dependabot configuration for updates
Configured Dependabot for version updates with a weekly schedule.
2025-09-24 17:45:01 +08:00
Vick Scarlet aee4fcf164 fix: 修复冲突天赋漏填出错 2025-05-17 21:27:01 +08:00
Vick Scarlet 5c0fe56915 fix: 修复冲突天赋漏填出错 2025-05-17 21:17:21 +08:00
Vick Scarlet b82da6c9a7 delete: unuse 2025-04-06 20:38:54 +08:00
su226 b1ffae9eaf fix: wrong data 2025-04-06 19:39:05 +08:00
Vick Scarlet fee986432d update: use min lib 2025-04-05 18:37:04 +08:00
Vick Scarlet 25fc74398a update: readme 2025-04-05 18:26:55 +08:00
Vick Scarlet 35c5fe36fb update: readme 2025-04-05 18:03:43 +08:00
Vick Scarlet 3f58e3deae fix: script 2025-04-05 17:58:08 +08:00
Vick Scarlet 788c864e71 change: vite build base 2025-04-05 17:47:35 +08:00
Vick Scarlet 680a4f8a5a remove: unuse 2025-04-05 17:43:21 +08:00
Vick Scarlet 8f46e286dd update: depence 2025-04-05 17:40:22 +08:00
Vick Scarlet 2eaf7a1ba2 update: toolchan 2025-04-05 17:27:00 +08:00
Vick Scarlet 6b58fa99cf change: ui 2025-04-05 12:35:59 +08:00
Vick Scarlet 5d1b623230 add: banner 2025-04-05 12:29:01 +08:00
Vick Scarlet 6e442c14c1 update: afd url 2024-09-17 18:31:30 +08:00
Vick Scarlet 2eadb6fb23 update event 2024-07-31 19:40:42 +08:00
Vick Scarlet d19afd7b1a update special thanks 2024-02-02 16:33:05 +08:00
Vick Scarlet 98eda94f66 update: special thanks 2023-10-12 19:07:46 +08:00
Vick Scarlet e0f5cb71c5 update specialthanks 2023-02-14 19:43:30 +08:00
Vick Scarlet 58140fd4d2 fix #379 2023-01-03 09:39:52 +08:00
Vick Scarlet b44e0e65a5 update: specialthanks 2023-01-03 09:38:51 +08:00
Vick Scarlet cc460d6108 update specialthanks 2022-11-15 22:02:46 +08:00
Vick Scarlet 1dd1927612 dev: update v-transform 2022-10-07 14:19:18 +08:00
Vick Scarlet 21d4c9d568 update specialthanks 2022-09-24 16:36:49 +08:00
Vick Scarlet 35eac7d19d update data 2022-09-12 14:05:09 +08:00
Vick Scarlet c1ed14385a update special thanks 2022-09-01 23:19:47 +08:00
Vick Scarlet 50ad3d35e7 update: events 2022-08-29 15:35:11 +08:00
Vick Scarlet 4df53d9f58 update specialthanks 2022-08-18 15:10:38 +08:00
Vick Scarlet 8a6f50dda6 update specialthanks 2022-07-16 13:32:10 +08:00
Miller-du 49488505e2 Update webpack.config.cjs 2022-06-08 11:39:31 +08:00
Vick Scarlet dd64e8f44e update specialthanks 2022-05-31 04:15:31 +08:00
Vick Scarlet b39772fd94 update event 2022-05-17 22:17:08 +08:00
Vick Scarlet fc1135a621 update new event 2022-04-28 01:21:05 +08:00
Vick Scarlet d7ea91a1b8 update specialthanks 2022-04-27 16:39:16 +08:00
Vick Scarlet 53a8729968 fix test 2022-04-07 15:44:22 +08:00
Vick Scarlet ad93703a45 update specialthanks 2022-04-07 15:21:56 +08:00
Vick Scarlet 329c2f09c4 update specialthanks 2022-03-30 15:20:38 +08:00
Vick Scarlet 1f7434a133 update specialthanks 2022-03-04 19:39:20 +08:00
Vick Scarlet 145211ba9a fix: #367 delete not exists branch 2022-02-21 10:13:31 +08:00
Vick Scarlet 1d58fd6bfd update achievement 2022-02-15 18:44:02 +08:00
Vick Scarlet 26d4e09bc8 update specialthanks 2022-02-14 22:52:08 +08:00
Vick Scarlet 49619fbaa2 Merge branch 'master' of https://github.com/VickScarlet/lifeRestart 2022-01-31 17:26:39 +08:00
Vick Scarlet 751a0dec0f hotfix 2022-01-31 17:24:29 +08:00
Vick Scarlet bc560c393b hotfix event
Squashed commit of the following:

commit f8724b9ffd
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Jan 29 01:08:39 2022 +0800

    update event
2022-01-29 01:13:37 +08:00
Vick Scarlet 8eb0e44845 Squashed commit of the following:
commit 89e30db1fb
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Fri Jan 28 13:49:32 2022 +0800

    update events

commit 870d711162ec9e86c67caba7b380ffe2e1f45970
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 27 16:50:19 2022 +0800

    update event

commit b327eb17653772c1c18be346295d55c1975e7bc9
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 27 16:36:02 2022 +0800

    update event

commit 4e845bc2eb6f5a50fd0e0bc65839e2f14219d9c3
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 27 16:35:56 2022 +0800

    fix ui feel

commit bd508913c5214e4869b75256e4998c0971d06f06
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 27 16:35:22 2022 +0800

    update special thanks

commit d097b3bc15b35acbfe8c3756b8c7860351b6c95c
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 27 16:04:55 2022 +0800

    add unique character

commit 8d71b73e94f10dc64f1661512c4dbfb750521032
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Jan 25 21:27:29 2022 +0800

    fix: bug
    save load

commit 286828abe74307ecd5d4ef730068fb8d7b5a4cdd
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Jan 25 18:15:22 2022 +0800

    add game mode

commit 0e3c885ad3
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 6 20:09:20 2022 +0800

    update special thanks

commit b421082374
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Fri Dec 31 20:26:20 2021 +0800

    update specialthanks

commit 765a69294b
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 19:10:03 2021 +0800

    fix ios goto

commit 8eb8c3ca72
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 19:03:37 2021 +0800

    add event grade

commit ab705dd46c
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 18:48:42 2021 +0800

    build version

commit a1e4232369
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 18:47:05 2021 +0800

    change talent replace before property allocate

commit f4d7b3ae3c
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 18:06:18 2021 +0800

    add event grade

commit 0567e40ddc
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 21:28:10 2021 +0800

    add particle

commit f17003925e
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 20:14:26 2021 +0800

    change ui

commit f8beda1d8ff0d4b7712d0ba5aff2b59624ded8cf
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 19:08:30 2021 +0800

    change popup message ui

commit 915a2aa17d5bf057282c0289671506f52f789008
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 18:34:29 2021 +0800

    new build

commit b5edabc10433c1dfe444a2c07ad1eaccc71ef188
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 18:18:46 2021 +0800

    add hint message

commit a143feadaf05adfef468d9b464252ad69a8ebc62
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 17:57:31 2021 +0800

    fix copy bug

commit eab1975791b13325dd687969f7a04fade15a3bd1
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 17:56:28 2021 +0800

    add save/load

commit acf9b9213451f0f3c754ab068a62b367a7103250
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 14:35:33 2021 +0800

    add theme switch

commit 488a6054ab
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Dec 1 18:30:36 2021 +0800

    fix uiManager bug

commit f0317eaf14
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Dec 1 16:07:03 2021 +0800

    add message popup

commit eb02d1ee6f
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 21:22:54 2021 +0800

    change relocation

commit 60503eae47
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 19:27:16 2021 +0800

    change ScaleButton -> Laya.runtime.ScaleButton

commit 52ef5909c1
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 19:26:31 2021 +0800

    change ScaleButton -> Laya.runtime.ScaleButton

commit 0284f6aa71
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 19:05:24 2021 +0800

    config webpack

commit 0e272ffd99
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 13:17:41 2021 +0800

    add thanks

commit e1e2ab4701
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 11:40:21 2021 +0800

    add light theme

commit 4f55c00f86
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 27 22:27:39 2021 +0800

    fix bug

commit 1d07bdfc46
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 27 21:48:47 2021 +0800

    daily update

commit 52d6a0ca8f
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Nov 24 23:10:04 2021 +0800

    daily update

commit f8dd720d4d
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Nov 24 19:08:13 2021 +0800

    daily update

commit d088c1a862
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 21 21:41:46 2021 +0800

    remove useless

commit a711fcf3ed
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Nov 16 00:53:05 2021 +0800

    add achievement popup
    add page adaptive
    add talent extends

commit 05e02de3db
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 13 01:24:04 2021 +0800

    add github and discord

commit bff0109cc5
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 13 00:30:01 2021 +0800

    add trajectory auto mode

commit 54c04f65c2
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Fri Nov 12 19:54:31 2021 +0800

    fix property judge negative number bug

commit 660739530b
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Fri Nov 12 19:48:58 2021 +0800

    update achievement

commit c94e9e7433
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Nov 11 21:37:45 2021 +0800

    update daily

commit c06cd9ea62
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Nov 9 00:08:05 2021 +0800

    update daily

commit 2666983f89
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 7 23:00:22 2021 +0800

    add uiManager auto scan and load skin resource

commit 22fa5d755a
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 7 22:17:04 2021 +0800

    update framework

commit f40698d63e
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Oct 31 00:13:22 2021 +0800

    add laya
2022-01-28 14:03:05 +08:00
Vick Scarlet 14f9340d0a Squashed commit of the following:
commit 870d711162ec9e86c67caba7b380ffe2e1f45970
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 27 16:50:19 2022 +0800

    update event

commit b327eb17653772c1c18be346295d55c1975e7bc9
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 27 16:36:02 2022 +0800

    update event

commit 4e845bc2eb6f5a50fd0e0bc65839e2f14219d9c3
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 27 16:35:56 2022 +0800

    fix ui feel

commit bd508913c5214e4869b75256e4998c0971d06f06
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 27 16:35:22 2022 +0800

    update special thanks

commit d097b3bc15b35acbfe8c3756b8c7860351b6c95c
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 27 16:04:55 2022 +0800

    add unique character

commit 8d71b73e94f10dc64f1661512c4dbfb750521032
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Jan 25 21:27:29 2022 +0800

    fix: bug
    save load

commit 286828abe74307ecd5d4ef730068fb8d7b5a4cdd
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Jan 25 18:15:22 2022 +0800

    add game mode

commit 0e3c885ad3
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Jan 6 20:09:20 2022 +0800

    update special thanks

commit b421082374
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Fri Dec 31 20:26:20 2021 +0800

    update specialthanks

commit 765a69294b
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 19:10:03 2021 +0800

    fix ios goto

commit 8eb8c3ca72
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 19:03:37 2021 +0800

    add event grade

commit ab705dd46c
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 18:48:42 2021 +0800

    build version

commit a1e4232369
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 18:47:05 2021 +0800

    change talent replace before property allocate

commit f4d7b3ae3c
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 18:06:18 2021 +0800

    add event grade

commit 0567e40ddc
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 21:28:10 2021 +0800

    add particle

commit f17003925e
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 20:14:26 2021 +0800

    change ui

commit f8beda1d8ff0d4b7712d0ba5aff2b59624ded8cf
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 19:08:30 2021 +0800

    change popup message ui

commit 915a2aa17d5bf057282c0289671506f52f789008
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 18:34:29 2021 +0800

    new build

commit b5edabc10433c1dfe444a2c07ad1eaccc71ef188
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 18:18:46 2021 +0800

    add hint message

commit a143feadaf05adfef468d9b464252ad69a8ebc62
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 17:57:31 2021 +0800

    fix copy bug

commit eab1975791b13325dd687969f7a04fade15a3bd1
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 17:56:28 2021 +0800

    add save/load

commit acf9b9213451f0f3c754ab068a62b367a7103250
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 14:35:33 2021 +0800

    add theme switch

commit 488a6054ab
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Dec 1 18:30:36 2021 +0800

    fix uiManager bug

commit f0317eaf14
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Dec 1 16:07:03 2021 +0800

    add message popup

commit eb02d1ee6f
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 21:22:54 2021 +0800

    change relocation

commit 60503eae47
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 19:27:16 2021 +0800

    change ScaleButton -> Laya.runtime.ScaleButton

commit 52ef5909c1
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 19:26:31 2021 +0800

    change ScaleButton -> Laya.runtime.ScaleButton

commit 0284f6aa71
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 19:05:24 2021 +0800

    config webpack

commit 0e272ffd99
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 13:17:41 2021 +0800

    add thanks

commit e1e2ab4701
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 11:40:21 2021 +0800

    add light theme

commit 4f55c00f86
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 27 22:27:39 2021 +0800

    fix bug

commit 1d07bdfc46
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 27 21:48:47 2021 +0800

    daily update

commit 52d6a0ca8f
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Nov 24 23:10:04 2021 +0800

    daily update

commit f8dd720d4d
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Nov 24 19:08:13 2021 +0800

    daily update

commit d088c1a862
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 21 21:41:46 2021 +0800

    remove useless

commit a711fcf3ed
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Nov 16 00:53:05 2021 +0800

    add achievement popup
    add page adaptive
    add talent extends

commit 05e02de3db
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 13 01:24:04 2021 +0800

    add github and discord

commit bff0109cc5
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 13 00:30:01 2021 +0800

    add trajectory auto mode

commit 54c04f65c2
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Fri Nov 12 19:54:31 2021 +0800

    fix property judge negative number bug

commit 660739530b
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Fri Nov 12 19:48:58 2021 +0800

    update achievement

commit c94e9e7433
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Nov 11 21:37:45 2021 +0800

    update daily

commit c06cd9ea62
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Nov 9 00:08:05 2021 +0800

    update daily

commit 2666983f89
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 7 23:00:22 2021 +0800

    add uiManager auto scan and load skin resource

commit 22fa5d755a
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 7 22:17:04 2021 +0800

    update framework

commit f40698d63e
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Oct 31 00:13:22 2021 +0800

    add laya
2022-01-28 00:39:35 +08:00
Vick Scarlet 150c991648 Squashed commit of the following:
commit b421082374
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Fri Dec 31 20:26:20 2021 +0800

    update specialthanks

commit 765a69294b
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 19:10:03 2021 +0800

    fix ios goto

commit 8eb8c3ca72
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 19:03:37 2021 +0800

    add event grade

commit ab705dd46c
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 18:48:42 2021 +0800

    build version

commit a1e4232369
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 18:47:05 2021 +0800

    change talent replace before property allocate

commit f4d7b3ae3c
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Dec 14 18:06:18 2021 +0800

    add event grade

commit 0567e40ddc
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 21:28:10 2021 +0800

    add particle

commit f17003925e
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 20:14:26 2021 +0800

    change ui

commit f8beda1d8ff0d4b7712d0ba5aff2b59624ded8cf
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 19:08:30 2021 +0800

    change popup message ui

commit 915a2aa17d5bf057282c0289671506f52f789008
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 18:34:29 2021 +0800

    new build

commit b5edabc10433c1dfe444a2c07ad1eaccc71ef188
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 18:18:46 2021 +0800

    add hint message

commit a143feadaf05adfef468d9b464252ad69a8ebc62
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 17:57:31 2021 +0800

    fix copy bug

commit eab1975791b13325dd687969f7a04fade15a3bd1
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 17:56:28 2021 +0800

    add save/load

commit acf9b9213451f0f3c754ab068a62b367a7103250
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Dec 2 14:35:33 2021 +0800

    add theme switch

commit 488a6054ab
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Dec 1 18:30:36 2021 +0800

    fix uiManager bug

commit f0317eaf14
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Dec 1 16:07:03 2021 +0800

    add message popup

commit eb02d1ee6f
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 21:22:54 2021 +0800

    change relocation

commit 60503eae47
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 19:27:16 2021 +0800

    change ScaleButton -> Laya.runtime.ScaleButton

commit 52ef5909c1
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 19:26:31 2021 +0800

    change ScaleButton -> Laya.runtime.ScaleButton

commit 0284f6aa71
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 19:05:24 2021 +0800

    config webpack

commit 0e272ffd99
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 13:17:41 2021 +0800

    add thanks

commit e1e2ab4701
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 28 11:40:21 2021 +0800

    add light theme

commit 4f55c00f86
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 27 22:27:39 2021 +0800

    fix bug

commit 1d07bdfc46
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 27 21:48:47 2021 +0800

    daily update

commit 52d6a0ca8f
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Nov 24 23:10:04 2021 +0800

    daily update

commit f8dd720d4d
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Wed Nov 24 19:08:13 2021 +0800

    daily update

commit d088c1a862
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 21 21:41:46 2021 +0800

    remove useless

commit a711fcf3ed
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Nov 16 00:53:05 2021 +0800

    add achievement popup
    add page adaptive
    add talent extends

commit 05e02de3db
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 13 01:24:04 2021 +0800

    add github and discord

commit bff0109cc5
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sat Nov 13 00:30:01 2021 +0800

    add trajectory auto mode

commit 54c04f65c2
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Fri Nov 12 19:54:31 2021 +0800

    fix property judge negative number bug

commit 660739530b
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Fri Nov 12 19:48:58 2021 +0800

    update achievement

commit c94e9e7433
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Thu Nov 11 21:37:45 2021 +0800

    update daily

commit c06cd9ea62
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Tue Nov 9 00:08:05 2021 +0800

    update daily

commit 2666983f89
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 7 23:00:22 2021 +0800

    add uiManager auto scan and load skin resource

commit 22fa5d755a
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Nov 7 22:17:04 2021 +0800

    update framework

commit f40698d63e
Author: Vick Scarlet <scarlet_vick@outlook.com>
Date:   Sun Oct 31 00:13:22 2021 +0800

    add laya
2021-12-31 20:31:26 +08:00
Vick Scarlet 99640a5813 update specialthanks 2021-12-23 07:21:54 +08:00
Vick Scarlet 485c1bb48c Merge branch 'master' of https://github.com/VickScarlet/lifeRestart 2021-12-11 13:00:28 +08:00
Vick Scarlet 5799cf635b update specialthanks 2021-12-11 13:00:20 +08:00
Jiang 066f1fd060 support build docker images (#358)
* add docker build file

* add host to avoid host check error

* Revert "add host to avoid host check error"

This reverts commit a0b8507895.
2021-12-10 17:38:45 +08:00
Vick Scarlet 162eaffd1d update specialthanks 2021-12-04 19:55:48 +08:00
Vick Scarlet b5a4db5224 new bundle 2021-11-24 11:24:01 +08:00
zhowiny 09b3aeb8c5 fix condition bug 2021-11-24 11:14:58 +08:00
Vick Scarlet c83ceef3c4 update data 2021-11-24 11:13:05 +08:00
Vick Scarlet 221e5219f0 update specialthanks 2021-11-16 23:50:47 +08:00
Vick Scarlet 145dfe964d update specialthanks 2021-11-10 16:11:26 +08:00
Vick Scarlet bf9702e3a1 update specialthanks 2021-11-05 02:04:48 +08:00
Vick Scarlet b3f1e6c9c5 update specialthanks 2021-10-31 22:20:17 +08:00
Vick Scarlet 960f3137c6 add halloween theme 2021-10-31 02:09:55 +08:00
Vick Scarlet e38daf3ebb update specialthanks 2021-10-28 23:21:20 +08:00
Vick Scarlet e72d16f811 update specialthanks 2021-10-25 01:44:34 +08:00
Vick Scarlet dea6001234 update specialthanks 2021-10-21 19:35:34 +08:00
Vick Scarlet 67d7584a6e update specialthanks 2021-10-15 22:05:31 +08:00
Vick Scarlet 417e386412 update specialthanks 2021-10-12 22:31:31 +08:00
Vick Scarlet c3d556ed80 update specialthanks 2021-10-11 11:45:41 +08:00
Vick Scarlet af24e69848 update specialthanks 2021-10-10 00:06:53 +08:00
Vick Scarlet 6d2f1df0da update specialthanks 2021-10-06 20:30:27 +08:00
Vick Scarlet 037db837b2 update specialthanks 2021-10-06 03:06:41 +08:00
Vick Scarlet e91fab8a94 add space to next 2021-10-06 02:21:18 +08:00
Vick Scarlet b65ef06acd update specialthanks 2021-10-04 10:21:26 +08:00
Vick Scarlet 10527e4480 update specialthanks 2021-10-02 18:09:56 +08:00
Nathan Du 25d1d4f4bc amend documentation 2021-10-02 16:34:50 +08:00
Nathan Du b8fcb7203d rename viewProperty to showProperty 2021-10-02 16:34:50 +08:00
Nathan Du 79e21a1156 allow viewing current attributes during trajectory[sic]
补上了查看当前属性的功能。
命令行端的功能现在应该和 web 端差不多了。
||为什么要给查看当前属性的功能取 getLastRecord 这样奇怪的名字||
2021-10-02 16:34:50 +08:00
Nathan Du 70d0b4460a use reverse video in place of background color
这个修改之前,稀有天赋之类的在某些终端配色方案下根本看不清写了什么。
改用反色后一般就没问题了。
2021-10-02 16:34:50 +08:00
Nathan Du 40b1ba3542 change aliases for /alloc
移除 allocation 指令,增加效果相同的 allocate,attrib 和 attribute 指令
使用动词替换名词作为指令的称呼我认为理所当然
把 attrib 和 attribute 定义为 alloc 的同义词是因为我想要添加在人生当中
知道自己现在属性的功能(web 版有的功能居然没有在命令行版中)
2021-10-02 16:34:50 +08:00
Nathan Du 274290cc5b default to next when no command is given
当你学会命令行版如何操作以后,n 就绝对会是你用到最多的指令(除非开 at)
为了减少不必要的操作,把 n 设成了默认操作
不过不知道会不会让本来开 at 的玩家选择按住回车呢(笑
2021-10-02 16:34:50 +08:00
Nathan Du fa6aa9bc7d tweak interface text and add command hint
指明在天赋抽卡、属性分配和继承天赋需要用的指令。
在英文单词和阿拉伯数字周围加了空格(因为好看!)
2021-10-02 16:34:50 +08:00
Nathan Du b18c41349d add help note
不打指令默认 help 对于目前可怕的交互而言还好,但……
2021-10-02 16:34:50 +08:00
Vick Scarlet b36f20c0d7 update specialthanks 2021-10-01 17:19:37 +08:00
Vick Scarlet 75a1d3c5aa update specialthanks 2021-09-29 20:40:15 +08:00
神戸小鳥 78ab0fee06 add other version 2021-09-28 22:35:11 +08:00
神戸小鳥 1b8fe068f7 add other version 2021-09-28 22:33:43 +08:00
qizhongchun 204635a84b 调整初始属性界面,PC或平板小屏颜值属性展示不完整 2021-09-28 22:25:33 +08:00
Vick Scarlet 21d6df56a4 update specialthanks 2021-09-27 22:09:06 +08:00
Vick Scarlet 514adffb9c update specialthanks 2021-09-26 21:37:48 +08:00
Vick Scarlet 0006634e5d update specialthanks 2021-09-25 22:03:13 +08:00
Vick Scarlet fe79670273 update specialthanks 2021-09-23 20:11:12 +08:00
Vick Scarlet f11af0da02 update specialthanks 2021-09-22 22:28:43 +08:00
Vick Scarlet fbd56604c0 update specialthanks 2021-09-21 22:30:08 +08:00
Vick Scarlet 296e2665b5 update specialthanks 2021-09-20 23:19:03 +08:00
Vick Scarlet 70b6129c6e change input number style 2021-09-20 23:14:03 +08:00
Vick Scarlet aa84c5f8a2 update specialthanks 2021-09-19 22:43:20 +08:00
Yurun 8b0a237ce4 属性输入框增加 type="number" 2021-09-19 13:48:30 +08:00
Vick Scarlet 6f89bc68fe update specialthanks 2021-09-18 22:13:01 +08:00
Vick Scarlet 48f7941e64 update specialthanks 2021-09-17 23:36:58 +08:00
Vick Scarlet 2557e76761 update data 2021-09-17 00:16:09 +08:00
Vick Scarlet b2d556a9f4 update specialthanks 2021-09-15 23:28:27 +08:00
Vick Scarlet b1faf87dc0 add S/L 2021-09-15 20:36:22 +08:00
Vick Scarlet 12b5d2d042 update xlsxTransform to vTransform 2021-09-15 13:56:55 +08:00
REN 64dc0a5b1d 修改了test文件的一处错误 2021-09-15 11:39:39 +08:00
Vick Scarlet 4e63afbfc6 update data 2021-09-14 22:01:20 +08:00
Vick Scarlet f8f863cada update data 2021-09-14 17:32:53 +08:00
qizhongchun 441e67a462 age 500 几率问题 2021-09-14 17:30:50 +08:00
Vick Scarlet c01cdfd4e6 update data 2021-09-14 13:06:21 +08:00
Vick Scarlet a4e609f927 update data 2021-09-13 22:29:37 +08:00
Vick Scarlet ccb8be24d5 update data 2021-09-13 20:59:01 +08:00
Vick Scarlet 8d4cd08e2e update data 2021-09-13 14:14:19 +08:00
Vick Scarlet a40c5e3951 update data 2021-09-13 10:55:32 +08:00
Vick Scarlet 06de7417a8 update data 2021-09-12 23:35:28 +08:00
Vick Scarlet b4d1d757e1 update data 2021-09-12 23:03:04 +08:00
Vick Scarlet 574df2b670 update data 2021-09-12 22:44:27 +08:00
Vick Scarlet a5c26a8d68 fix replacement data 2021-09-12 22:23:46 +08:00
Vick Scarlet 866acdb8b2 update data
add replacemant talent
add random property
2021-09-12 22:04:45 +08:00
RomEYer-AwELF b833713afe 为什么布局又乱了呢? 2021-09-12 19:29:35 +08:00
Vick Scarlet 7db04c03d7 update data 2021-09-12 17:49:14 +08:00
Vick Scarlet b13c6aa488 update data 2021-09-12 11:03:09 +08:00
Vick Scarlet 1c1a2315a0 update data 2021-09-12 10:47:39 +08:00
Vick Scarlet 38ac352795 update data 2021-09-11 22:21:18 +08:00
Vick Scarlet 2c9392de8d update data 2021-09-11 18:11:38 +08:00
Vick Scarlet 71507e0164 update data 2021-09-11 18:01:34 +08:00
Vick Scarlet 74e8a7b34f update data 2021-09-11 17:07:31 +08:00
Vick Scarlet 5031b089ff update data 2021-09-11 16:39:36 +08:00
Vick Scarlet 8a265a2e84 update data 2021-09-11 16:29:59 +08:00
Vick Scarlet fb7d78ef83 update data 2021-09-11 16:06:52 +08:00
Vick Scarlet 65be1b4f40 fix lifeProperty 2021-09-11 12:46:39 +08:00
Vick Scarlet 77f483bd18 update data 2021-09-11 11:24:36 +08:00
Vick Scarlet a52178b5f8 update data 2021-09-11 11:01:07 +08:00
Vick Scarlet 1e4670c112 update csv 2021-09-11 10:49:30 +08:00
Vick Scarlet ed0a5de025 update data 2021-09-11 10:22:58 +08:00
Vick Scarlet c73d2ebae6 update data 2021-09-11 10:21:07 +08:00
Vick Scarlet f048e0bbc2 update data 2021-09-11 10:05:52 +08:00
Vick Scarlet 7654b2a212 update data 2021-09-11 09:45:44 +08:00
Dafrok cf1acb64bf refine: button selection style 2021-09-11 01:41:01 +08:00
Vick Scarlet 6433c737d7 add rank btn 2021-09-10 21:45:55 +08:00
Zhuobin Zhang 1e816faf00 more conspicuous button selection state 2021-09-10 18:28:38 +08:00
Zhuobin Zhang e876fc7768 fix: missing public path '/lib' in webpack config 2021-09-10 18:28:38 +08:00
Vick Scarlet a59bce865f update data 2021-09-10 15:42:42 +08:00
Vick Scarlet 1003a78a2a add autoplay auto selece extend 2021-09-10 15:00:59 +08:00
194 changed files with 12247 additions and 83970 deletions
+3
View File
@@ -0,0 +1,3 @@
paths-ignore:
- 'public/libs/**'
- 'tests'
+11
View File
@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
+37
View File
@@ -0,0 +1,37 @@
name: Build and Deploy
on:
push:
branches: [main]
permissions:
contents: write
jobs:
build-and-deploy:
concurrency: ci-${{ github.ref }}
runs-on: ubuntu-latest
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v4
- name: 🛠️ Install Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: 🛠️ Install pnpm
uses: pnpm/action-setup@v4
with:
version: latest
- name: 📦 Install Dependencies
run: pnpm install
- name: 🏗️ Build data
run: bun build:data
- name: 🏗️ Build web
run: bun build:web
- name: 🚀 Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: apps/web/dist # The folder the action should deploy.
-31
View File
@@ -1,31 +0,0 @@
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm install
- run: npm run build --if-present
- run: npm test
+4 -1
View File
@@ -107,4 +107,7 @@ utils/xlsxTransform-*
/.idea
__localStorage.json
__localStorage.json
template/public
public/data
-3
View File
@@ -1,3 +0,0 @@
{
"esversion": 9
}
+8 -20
View File
@@ -4,33 +4,21 @@
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Editor Browser",
"request": "launch",
"type": "editor-browser",
"url": "http://localhost:5173",
},
{
"type": "node",
"request": "launch",
"name": "test",
"program": "${workspaceFolder}/test",
"skipFiles": [
"<node_internals>/**"
"<node_internals>/**",
"**/node_modumes/**"
]
},
{
"type": "node",
"request": "launch",
"name": "xlsxTransform",
"program": "${workspaceFolder}/utils/xlsxTransform.js",
"args": ["data"],
"skipFiles": [
"<node_internals>/**"
]
},
{
"name": "Attach by Process ID",
"processId": "${command:PickProcess}",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
},
]
}
+3
View File
@@ -0,0 +1,3 @@
{
"js/ts.experimental.useTsgo": true
}
-1
View File
@@ -1 +0,0 @@
liferestart.syaro.io
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 神戸小鳥
Copyright (c) 2021-2026 神戸小鳥 and 神户小德
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+8 -51
View File
@@ -1,62 +1,19 @@
# Life Restart
# 人生重开模拟器
<a href="https://discord.gg/U3qrf49NMQ"><img src="https://img.shields.io/discord/883382868427014255?color=%23FEE75C&label=Discord&logo=discord&logoColor=white&style=for-the-badge" /></a>
[English](./README.md) | 简体中文
## 简介
## 开发环境
- Game Life Restart
包管理: `pnpm`
运行时: `bun`
## 使用
<details>
<summary><strong>网页版</strong></summary>
<br />
1. 下载项目代码。
### 开发
```bash
git clone https://github.com/VickScarlet/lifeRestart.git my-project
cd my-project
```
2. 进入目录安装依赖。
```bash
yarn install
```
或者
```bash
npm install
```
3. 启动本地服务器。
```bash
yarn dev
```
或者
```bash
npm run dev
```
4. 启动完成后会自动打开浏览器访问 [http://localhost:8081/view/index.html](http://localhost:8081/view/index.html)。
</details>
<details>
<summary><strong>控制台版本</strong></summary>
<br />
```bash
node repl
```
</details>
> 更多信息请参考 [官网文档](https://liferestart.syaro.io/)。
pnpm build:data
pnpm dev
```
+8 -50
View File
@@ -1,61 +1,19 @@
# Life Restart
# Remake
<a href="https://discord.gg/U3qrf49NMQ"><img src="https://img.shields.io/discord/883382868427014255?color=%23FEE75C&label=Discord&logo=discord&logoColor=white&style=for-the-badge" /></a>
English | [简体中文](./README-zh_CN.md)
## Introduction
## Environment
- Game Life Restart
Package Manager: `pnpm`
Runtime: `bun`
## Usage
<details>
<summary><strong>Web Version</strong></summary>
<br />
1. Clone project code.
### Dev
```bash
git clone git@github.com:VickScarlet/lifeRestart.git my-project
cd my-project
```
2. Installation dependence.
```bash
yarn install
```
Or
```bash
npm install
```
3. Start local server.
```bash
yarn dev
```
Or
```bash
npm run dev
```
4. After the startup is complete, will automatically open a browser and visit [http://localhost:8081/view/index.html](http://localhost:8081/view/index.html).
</details>
<details>
<summary><strong>Command Line Version</strong></summary>
<br />
```bash
node repl
```
</details>
> More instructions at [documentation](https://liferestart.syaro.io/).
pnpm build:data
pnpm dev
```
-1
View File
@@ -1 +0,0 @@
theme: jekyll-theme-cayman
-43
View File
@@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="{{ site.lang | default: "en-US" }}">
<head>
<meta charset="UTF-8">
<script data-ad-client="ca-pub-9857163863537600" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
{% seo %}
<link rel="preconnect" href="https://fonts.gstatic.com">
<link rel="preload" href="https://fonts.googleapis.com/css?family=Open+Sans:400,700&display=swap" as="style" type="text/css" crossorigin>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#157878">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="stylesheet" href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}">
{% include head-custom.html %}
</head>
<body>
<a id="skip-to-content" href="#content">Skip to the content.</a>
<header class="page-header" role="banner">
<h1 class="project-name">{{ page.title | default: site.title | default: site.github.repository_name }}</h1>
<h2 class="project-tagline">{{ page.description | default: site.description | default: site.github.project_tagline }}</h2>
<a href="/view/" class="btn">RESTART</a>
<a href="/view/test.html" class="btn">Source Version</a>
{% if site.github.is_project_page %}
<a href="{{ site.github.repository_url }}" class="btn">View on GitHub</a>
{% endif %}
{% if site.show_downloads %}
<a href="{{ site.github.zip_url }}" class="btn">Download .zip</a>
<a href="{{ site.github.tar_url }}" class="btn">Download .tar.gz</a>
{% endif %}
</header>
<main id="content" class="main-content" role="main">
{{ content }}
<footer class="site-footer">
{% if site.github.is_project_page %}
<span class="site-footer-owner"><a href="{{ site.github.repository_url }}">{{ site.github.repository_name }}</a> is maintained by <a href="{{ site.github.owner_url }}">{{ site.github.owner_name }}</a>.</span>
{% endif %}
<span class="site-footer-credits">This page was generated by <a href="https://pages.github.com">GitHub Pages</a>.</span>
</footer>
</main>
</body>
</html>
+870
View File
@@ -0,0 +1,870 @@
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
import { readFile } from 'fs/promises'
import Life from '../srco/modules/life.js'
import $lang from '../srco/i18n/zh-cn.js'
globalThis.$lang = $lang
globalThis.json = async fileName =>
JSON.parse(await readFile(`${__dirname}/../public/data/${fileName}.json`))
globalThis.$$eventMap = new Map()
globalThis.$$event = (tag, data) => {
const listener = $$eventMap.get(tag)
if (listener) listener.forEach(fn => fn(data))
}
globalThis.$$on = (tag, fn) => {
let listener = $$eventMap.get(tag)
if (!listener) {
listener = new Set()
$$eventMap.set(tag, listener)
}
listener.add(fn)
}
globalThis.$$off = (tag, fn) => {
const listener = $$eventMap.get(tag)
if (listener) listener.delete(fn)
}
class App {
constructor() {
this.#life.config({
defaultPropertyPoints: 20, // default number of points for a property
talentSelectLimit: 3, // max number of talents that can be selected
propertyAllocateLimit: [0, 10], // scoop of properties that can be allocated,
defaultPropertys: { SPR: 5 }, // default properties
talentConfig: {
// config for talent
talentPullCount: 10, // number of talents to pull from the talent pool
talentRate: { 1: 100, 2: 10, 3: 1, total: 1000 }, // rate of talent pull
additions: {
TMS: [
[10, { 2: 1 }],
[30, { 2: 2 }],
[50, { 2: 3 }],
[70, { 2: 4 }],
[100, { 2: 5 }],
],
CACHV: [
[10, { 2: 1 }],
[30, { 2: 2 }],
[50, { 2: 3 }],
[70, { 2: 4 }],
[100, { 2: 5 }],
],
},
},
propertyConfig: {
// config for property
judge: {
// type: [min, grade, judge]
RTLT: [
[0, 0],
[0.3, 1],
[0.6, 2],
[0.9, 3],
],
REVT: [
[0, 0],
[0.2, 1],
[0.4, 2],
[0.6, 3],
],
TMS: [
[0, 0, 'UI_Remake_Times_Judge_Level_0'],
[10, 1, 'UI_Remake_Times_Judge_Level_1'],
[30, 1, 'UI_Remake_Times_Judge_Level_2'],
[50, 2, 'UI_Remake_Times_Judge_Level_3'],
[70, 2, 'UI_Remake_Times_Judge_Level_4'],
[100, 3, 'UI_Remake_Times_Judge_Level_5'],
],
CACHV: [
[0, 0, 'UI_Achievement_Count_Judge_Level_0'],
[10, 1, 'UI_Achievement_Count_Judge_Level_1'],
[30, 1, 'UI_Achievement_Count_Judge_Level_2'],
[50, 2, 'UI_Achievement_Count_Judge_Level_3'],
[70, 2, 'UI_Achievement_Count_Judge_Level_4'],
[100, 3, 'UI_Achievement_Count_Judge_Level_5'],
],
HCHR: [
[0, 0, 'UI_Judge_Level_0'],
[1, 0, 'UI_Judge_Level_1'],
[2, 0, 'UI_Judge_Level_2'],
[4, 0, 'UI_Judge_Level_3'],
[7, 1, 'UI_Judge_Level_4'],
[9, 2, 'UI_Judge_Level_5'],
[11, 3, 'UI_Judge_Level_6'],
],
HMNY: [
[0, 0, 'UI_Judge_Level_0'],
[1, 0, 'UI_Judge_Level_1'],
[2, 0, 'UI_Judge_Level_2'],
[4, 0, 'UI_Judge_Level_3'],
[7, 1, 'UI_Judge_Level_4'],
[9, 2, 'UI_Judge_Level_5'],
[11, 3, 'UI_Judge_Level_6'],
],
HSPR: [
[0, 0, 'UI_Spirit_Judge_Level_0'],
[1, 0, 'UI_Spirit_Judge_Level_1'],
[2, 0, 'UI_Spirit_Judge_Level_2'],
[4, 0, 'UI_Spirit_Judge_Level_3'],
[7, 1, 'UI_Spirit_Judge_Level_4'],
[9, 2, 'UI_Spirit_Judge_Level_5'],
[11, 3, 'UI_Spirit_Judge_Level_6'],
],
HINT: [
[0, 0, 'UI_Judge_Level_0'],
[1, 0, 'UI_Judge_Level_1'],
[2, 0, 'UI_Judge_Level_2'],
[4, 0, 'UI_Judge_Level_3'],
[7, 1, 'UI_Judge_Level_4'],
[9, 2, 'UI_Judge_Level_5'],
[11, 3, 'UI_Judge_Level_6'],
[21, 3, 'UI_Intelligence_Judge_Level_7'],
[131, 3, 'UI_Intelligence_Judge_Level_8'],
[501, 3, 'UI_Intelligence_Judge_Level_9'],
],
HSTR: [
[0, 0, 'UI_Judge_Level_0'],
[1, 0, 'UI_Judge_Level_1'],
[2, 0, 'UI_Judge_Level_2'],
[4, 0, 'UI_Judge_Level_3'],
[7, 1, 'UI_Judge_Level_4'],
[9, 2, 'UI_Judge_Level_5'],
[11, 3, 'UI_Judge_Level_6'],
[21, 3, 'UI_Strength_Judge_Level_7'],
[101, 3, 'UI_Strength_Judge_Level_8'],
[401, 3, 'UI_Strength_Judge_Level_9'],
[1001, 3, 'UI_Strength_Judge_Level_10'],
[2001, 3, 'UI_Strength_Judge_Level_11'],
],
HAGE: [
[0, 0, 'UI_AGE_Judge_Level_0'],
[1, 0, 'UI_AGE_Judge_Level_1'],
[10, 0, 'UI_AGE_Judge_Level_2'],
[18, 0, 'UI_AGE_Judge_Level_3'],
[40, 0, 'UI_AGE_Judge_Level_4'],
[60, 1, 'UI_AGE_Judge_Level_5'],
[70, 1, 'UI_AGE_Judge_Level_6'],
[80, 2, 'UI_AGE_Judge_Level_7'],
[90, 2, 'UI_AGE_Judge_Level_8'],
[95, 3, 'UI_AGE_Judge_Level_9'],
[100, 3, 'UI_AGE_Judge_Level_10'],
[500, 3, 'UI_AGE_Judge_Level_11'],
],
SUM: [
[0, 0, 'UI_Judge_Level_0'],
[41, 0, 'UI_Judge_Level_1'],
[50, 0, 'UI_Judge_Level_2'],
[60, 0, 'UI_Judge_Level_3'],
[80, 1, 'UI_Judge_Level_4'],
[100, 2, 'UI_Judge_Level_5'],
[110, 3, 'UI_Judge_Level_6'],
[120, 3, 'UI_Judge_Level_7'],
],
},
},
characterConfig: {
// config for character
characterPullCount: 3,
rateableKnife: 10,
propertyWeight: [
[0, 1],
[1, 2],
[2, 3],
[3, 4],
[4, 5],
[5, 6],
[6, 5],
[7, 4],
[8, 3],
[9, 2],
[10, 1],
],
talentWeight: [
[1, 1],
[2, 2],
[3, 3],
[4, 2],
[5, 1],
],
},
})
}
Steps = {
TALENT: 'talent',
PROPERTY: 'property',
TRAJECTORY: 'trajectory',
SUMMARY: 'summary',
}
#language = 'zh-cn'
#step = this.Steps.SUMMARY
#life = new Life()
#talentSelected = new Set()
#talentExtend = new Set()
#auto
#isEnd
#propertyAllocation
#output
#exit
#interval
#style = {
warn: ['\x1B[93m', '\x1B[39m'], // Bright Yellow
grade1: ['\x1B[94m', '\x1B[39m'], // Bright Blue
grade2: ['\x1B[95m', '\x1B[39m'], // Bright Magenta
grade3: ['\x1B[93m', '\x1B[39m'], // Bright Yellow
grade1b: ['\x1B[94m\x1B[7m', '\x1B[0m'], // Bright Blue BG
grade2b: ['\x1B[95m\x1B[7m', '\x1B[0m'], // Bright Magenta BG
grade3b: ['\x1B[93m\x1B[7m', '\x1B[0m'], // Bright Yellow BG
}
#randomTalents
style(type, str) {
const style = this.#style[type]
if (!style) return str
return `${style[0]}${str}${style[1]}`
}
async initial() {
this.output('Now Loading...')
this.#talentExtend = localStorage.talentExtend
await this.#life.initial(
dataSet => json(`${this.#language}/${dataSet}`),
json,
)
this.output(
`\rLoading Complete.
人生重开模拟器
这垃圾人生一秒也不想待了
键入 \x1B[4m/remake\x1B[24m 开始游戏
键入 \x1B[4m/help\x1B[24m 获取帮助`,
true,
)
$$on('achievement', ({ name }) =>
this.output(`
-------------------------
解锁成就【${name}
-------------------------
`),
)
}
io(input, output, exit) {
this.#output = output
this.#exit = exit
input(command => {
const ret = this.repl(command)
if (!ret) return
if (typeof ret == 'string') return this.output(ret, true)
if (Array.isArray(ret)) return this.output(...ret)
const { message, isRepl } = ret
return this.output(message, isRepl)
})
}
output(data, isRepl) {
if (!this.#output) return
this.#output(data, isRepl)
}
exit(code) {
if (this.#exit) this.#exit(code)
process.exit(code)
}
repl(command) {
command = command.split(/\s+/)
switch (command.shift()) {
case 'r':
case 'remake':
case '/remake':
return this.remake()
case 's':
case 'select':
case '/select':
return this.select(...command)
case 'u':
case 'unselect':
case '/unselect':
return this.unselect(...command)
case 'n':
case 'next':
case '/next':
case '':
return this.next(true)
case 'a':
case 'alloc':
case 'allocate':
case 'attrib':
case 'attribute':
case '/alloc':
case '/allocate':
case '/attrib':
case '/attribute':
return this.attrib(...command)
case 'rd':
case 'random':
case '/random':
return this.random()
case 'at':
case 'auto':
case '/auto':
return this.auto(...command)
case 'x':
case 'exit':
case '/exit':
return this.exit(0)
case '?':
case 'h':
case 'help':
case '/?':
case '/h':
case '/help':
default:
return this.help(...command)
}
}
help(key) {
switch (key) {
case 'x':
case 'exit':
case '/exit':
return `退出
x, exit, /exit 命令同等效果`
case 'r':
case 'remake':
case '/remake':
return `重开
r, remake, /remake 命令同等效果`
case 's':
case 'select':
case '/select':
return `选择
s, select, /select 命令同等效果
Example: /select 1 2 3 意味着选择 1 2 3 三个天赋
/select <id1> [id2] [id3]
参数解释 <id1> 通常来说要指定至少一个id
虽然不指定也可以
[id2]
[id3] 可以不指定`
case 'u':
case 'unselect':
case '/unselect':
return `取消选择
u, unselect,
/unselect 命令同等效果
Example: /unselect 1 2 3
意味着取消选择 1 2 3 三个天赋
参数解释 /unselect <id1> [id2] [id3]
<id1> 通常来说要指定至少一个id
虽然不指定也可以
[id2]
[id3] 可以不指定`
case 'a':
case 'alloc':
case 'allocate':
case 'attrib':
case 'attribute':
case '/alloc':
case '/allocate':
case '/attrib':
case '/attribute':
return `分配或查看属性点
a, alloc, allocate, attrib, attribute
/alloc, /allocate, /attrib, /attribute 命令同等效果
Example: /attribute
/allocate STR 1
/allocate INT -3
/allocate CHR +5
效果 在属性分配时分配属性点
在人生的过程中查看当前属性点
参数解释 /allocate <TAG> <[+/-]value>
<TAG> 表示要分配的属性标签
可选有
CHR, chr, c, C 表示颜值
INT, int, i, I 表示智力
STR, str, s, S 表示体质
MNY, mny, m, M 表示家境
必填
<[+/-]value>
表示属性的调整
其中
+ 表示在当前基础上增加
- 表示在当前基础上减少
无符号表示直接设置为此值
必填`
case 'n':
case 'next':
case '/next':
return `继续
n, next, /next 命令同等效果
效果 通常用于各步骤结束后
例如: 选择天赋后
分配属性后
每个年龄事件后
总评后
继承天赋后`
case 'at':
case 'auto':
case '/auto':
return `自动播放
at, auto, /auto 命令同等效果
效果 用于人生的过程中
每个年龄会自动下一年
播放速度 1 秒 1 年`
case '?':
case 'h':
case 'help':
case '/?':
case '/h':
case '/help':
return `显示帮助
, h, help
/?, /h, /help 命令同等效果
Example: /help
/help /select
参数解释 /help [command]
[command] 要详细显示帮助的命令
可以不填`
}
return `Help ---
命令 说明 示例
x
exit
/exit 退出 /exit
r
remake
/remake 重开 /remake
s
select
/select 选择天赋 /select <id1> [id2] [id3]
u
unselect
/unselect 取消选择 /unselect <id1> [id2] [id3]
a
alloc
allocate
attrib
attribute
/alloc
/allocate
/attrib
/attribute 分配或查看属性点 /allocate <TAG> <[+/-]value>
n
next
/next 继续 /next
at
auto
/auto 自动播放 /auto
?
h
help
/?
/h
/help 显示帮助 /help [command]`
}
auto(arg) {
this.#auto = arg != 'off'
return this.next(true)
}
remake() {
if (this.#talentExtend) {
this.#life.talentExtend(this.#talentExtend)
dumpLocalStorage()
this.#talentExtend = null
}
this.#isEnd = false
this.#talentSelected.clear()
this.#propertyAllocation = {
CHR: 0,
INT: 0,
STR: 0,
MNY: 0,
SPR: 5,
}
this.#step = this.Steps.TALENT
this.#randomTalents = this.#life.talentRandom()
return this.list()
}
select(...select) {
switch (this.#step) {
case this.Steps.TALENT:
return this.talentSelect(...select)
case this.Steps.SUMMARY:
return this.talentExtend(...select)
}
}
unselect(...select) {
switch (this.#step) {
case this.Steps.TALENT:
return this.talentUnSelect(...select)
case this.Steps.SUMMARY:
return this.talentExtendCancle(...select)
}
}
talentSelect(...select) {
const warn = str => `${this.list()}\n${this.style('warn', str)}`
for (const number of select) {
const s = this.#randomTalents[number]
if (!s) return warn(`${number} 为未知天赋`)
if (this.#talentSelected.has(s)) continue
if (this.#talentSelected.size == 3)
return warn(
'你只能选3个天赋。请使用 \x1B[4m/unselect\x1B[24m 取消选择你不想要的天赋',
)
const exclusive = this.#life.exclude(
Array.from(this.#talentSelected).map(({ id }) => id),
s.id,
)
if (exclusive != null)
for (const { name, id } of this.#talentSelected)
if (id == exclusive)
return warn(
`天赋【${s.name}】与已选择的天赋【${name}】冲突`,
)
this.#talentSelected.add(s)
}
return this.list()
}
talentUnSelect(...select) {
for (const number of select) {
const s = this.#randomTalents[number]
if (this.#talentSelected.has(s)) this.#talentSelected.delete(s)
}
return this.list()
}
talentExtend(select) {
const warn = str => `${this.list()}\n${this.style('warn', str)}`
const list = Array.from(this.#talentSelected)
const s = list[select]
if (!s) return warn(`${select} 为未知天赋`)
this.#talentExtend = s.id
return this.list()
}
talentExtendCancle() {
this.#talentExtend = null
return this.list()
}
list() {
let description, list, check
switch (this.#step) {
case this.Steps.TALENT:
description = '🎉 请选择(\x1B[4m/select\x1B[24m3 个天赋'
list = this.#randomTalents
check = talent => this.#talentSelected.has(talent)
break
case this.Steps.SUMMARY:
description =
'🎉 你可以选(\x1B[4m/select\x1B[24m)一个天赋继承'
list = Array.from(this.#talentSelected)
check = ({ id }) => this.#talentExtend == id
break
}
if (!list) return ''
return [
description,
list.map((talent, i) =>
this.style(
`grade${talent.grade}b`,
`${check(talent) ? '√' : ' '} ${i} ${talent.name}${
talent.description
}`,
),
),
]
.flat()
.join('\n')
}
next(enter) {
const warn = (a, b) =>
`${a}\n${this.style('warn', this.style('warn', b))}`
switch (this.#step) {
case this.Steps.TALENT:
if (this.#talentSelected.size != 3)
return warn(this.list(), `请选择 3 个天赋`)
this.#step = this.Steps.PROPERTY
this.#life.remake(
Array.from(this.#talentSelected).map(({ id }) => id),
)
this.#propertyAllocation.total = this.#life.getPropertyPoints()
return this.prop()
case this.Steps.PROPERTY:
const less = this.less()
if (less > 0)
return warn(this.prop(), `你还有 ${less} 属性点没有分配完`)
this.#step = this.Steps.TRAJECTORY
delete this.#propertyAllocation.total
this.#life.start(this.#propertyAllocation)
return this.trajectory(enter)
case this.Steps.TRAJECTORY:
if (!this.#isEnd) return this.trajectory(enter)
this.#step = this.Steps.SUMMARY
return `${this.summary()}\n\n${this.list()}`
case this.Steps.SUMMARY:
return this.remake()
}
}
trajectory(enter) {
if (enter) {
if (this.#interval) {
clearInterval(this.#interval)
this.#interval = null
this.#auto = false
} else if (this.#auto) {
this.#interval = setInterval(
() => {
const trajectory = this.next()
if (this.#isEnd && this.#interval) {
clearInterval(this.#interval)
this.#interval = null
}
if (!this.#isEnd) return this.output(`${trajectory}\n`)
return this.output(trajectory, true)
},
Number(this.#auto) || 1000,
)
return
}
}
const trajectory = this.#life.next()
const { age, content, isEnd } = trajectory
if (isEnd) this.#isEnd = true
return `${age}岁:\t${content
.map(({ type, description, grade, name, postEvent }) => {
switch (type) {
case 'TLT':
return `${name}${description}`
case 'EVT':
return (
description + (postEvent ? `\n\t${postEvent}` : '')
)
}
})
.join('\n\t')}`
}
prop() {
const { CHR, INT, STR, MNY } = this.#propertyAllocation
return `🎉 属性分配
请使用 \x1B[4m/alloc\x1B[24m <TAG> <value> 分配属性
剩余点数 ${this.less()}
属性(TAG) 当前值
颜值(CHR) ${CHR}
智力(INT) ${INT}
体质(STR) ${STR}
家境(MNY) ${MNY}
`
}
less() {
const { total, CHR, INT, STR, MNY } = this.#propertyAllocation
return total - CHR - INT - STR - MNY
}
attrib(tag, value) {
switch (this.#step) {
case this.Steps.PROPERTY:
return this.alloc(tag, value)
case this.Steps.TRAJECTORY:
return this.showProperty()
default:
return undefined
}
}
showProperty() {
let property = this.#life.getLastRecord()
return `当前属性
属性(TAG) 当前值
颜值(CHR) ${property.CHR}
智力(INT) ${property.INT}
体质(STR) ${property.STR}
家境(MNY) ${property.MNY}
快乐(SPR) ${property.SPR}`
}
alloc(tag, value) {
const warn = str => `${this.prop()}\n${this.style('warn', str)}`
if (!value) return warn('⚠ 分配的数值没有给定')
const isSet = !(value[0] == '-' || value[0] == '+')
value = Number(value)
if (isNaN(value)) return warn('⚠ 分配的数值不正确')
switch (tag) {
case 'c':
case 'chr':
case 'C':
tag = 'CHR'
break
case 'i':
case 'int':
case 'I':
tag = 'INT'
break
case 's':
case 'S':
case 'str':
tag = 'STR'
break
case 'm':
case 'M':
case 'mny':
tag = 'MNY'
break
}
switch (tag) {
case 'CHR':
case 'INT':
case 'STR':
case 'MNY':
break
default:
return warn('⚠ 未知的tag')
}
if (isSet) value = value - this.#propertyAllocation[tag]
const tempLess = this.less() - value
const tempSet = this.#propertyAllocation[tag] + value
if (tempLess < 0) return warn('⚠ 你没有更多的点数可以分配了')
if (tempLess > this.#propertyAllocation.total || tempSet < 0)
return warn('⚠ 不能分配负数属性')
if (tempSet > 10) return warn('⚠ 单项属性最高分配10点')
this.#propertyAllocation[tag] += value
return this.prop()
}
random() {
switch (this.#step) {
case this.Steps.TALENT:
this.#talentSelected.clear()
Array.from(this.#randomTalents)
.sort(() => Math.random() - 0.5)
.slice(0, 3)
.forEach(talent => {
this.#talentSelected.add(talent)
})
return this.list()
case this.Steps.PROPERTY:
let t = this.#propertyAllocation.total
const arr = [10, 10, 10, 10]
while (t > 0) {
const sub =
Math.round(Math.random() * (Math.min(t, 10) - 1)) + 1
while (true) {
const select = Math.floor(Math.random() * 4) % 4
if (arr[select] - sub < 0) continue
arr[select] -= sub
t -= sub
break
}
}
this.#propertyAllocation.CHR = 10 - arr[0]
this.#propertyAllocation.INT = 10 - arr[1]
this.#propertyAllocation.STR = 10 - arr[2]
this.#propertyAllocation.MNY = 10 - arr[3]
return this.prop()
case this.Steps.SUMMARY:
this.#talentExtend = Array.from(this.#talentSelected).sort(
() => Math.random() - 0.5,
)[0].id
return this.list()
}
}
summary() {
const summaryData = this.#life.summary
const format = (name, type) => {
const { judge, grade, value } = summaryData[type]
return this.style(
`grade${grade}b`,
`${name}${value} ${$lang[judge]}`,
)
}
return [
'🎉 总评',
format('颜值', this.#life.PropertyTypes.HCHR),
format('智力', this.#life.PropertyTypes.HINT),
format('体质', this.#life.PropertyTypes.HSTR),
format('家境', this.#life.PropertyTypes.HMNY),
format('快乐', this.#life.PropertyTypes.HSPR),
format('享年', this.#life.PropertyTypes.HAGE),
format('总评', this.#life.PropertyTypes.SUM),
].join('\n')
}
}
export default App
+44
View File
@@ -0,0 +1,44 @@
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
import App from './app.js'
import { readFile, writeFile } from 'fs/promises'
async function main() {
try {
globalThis.localStorage = JSON.parse(
await readFile(__dirname + '/__localStorage.json')
)
} catch (e) {
globalThis.localStorage = {}
}
localStorage.getItem = key =>
localStorage[key] === void 0 ? null : localStorage[key]
localStorage.setItem = (key, value) => (localStorage[key] = value)
globalThis.dumpLocalStorage = async () =>
await writeFile(
__dirname + '/__localStorage.json',
JSON.stringify(global.localStorage)
)
const app = new App()
app.io(
repl => process.stdin.on('data', data => repl(data.toString().trim())),
(data, isRepl) => process.stdout.write(`${data}${isRepl ? '\n>' : ''}`),
code => process.exit(code)
)
await app.initial()
}
main()
// process.stdin.setRawMode(true);
// process.openStdin().on('keypress', function (chunk, key) {
// process.stdout.write('Get Chunk: ' + chunk + '\n');
// if (key && key.ctrl && key.name == 'c') process.exit();
// });
+15
View File
@@ -0,0 +1,15 @@
{
"name": "@remake/console",
"type": "module",
"version": "3.0.0",
"author": "Vick Scarlet <vick@syaro.io>",
"scripts": {
"start": "bun run src/index.ts",
"lint": "bunx --bun eslint .",
"test": "bun test"
},
"dependencies": {
"@remake/core": "workspace:*",
"@remake/data": "workspace:*"
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["**/*"]
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from 'eslint/config'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import defaultConfig from '../../eslint.config.ts'
export default defineConfig([
defaultConfig,
{ ignores: ['dist/**', 'node_modules/**'] },
{
...react.configs.flat.recommended!,
settings: { react: { version: '19' } },
},
react.configs.flat['jsx-runtime']!,
reactHooks.configs.flat.recommended,
])
+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="description" content="人生重开模拟器 - 这垃圾人生一秒也不想待了"/>
<meta name="keywords" content="人生重开模拟器 liferestart life restart remake 人生重来"/>
<meta name="color-scheme" content="light dark" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
<title>人生重开模拟器</title>
</head>
<body>
<div id="remake"></div>
<script type="module" src="src/index.tsx"></script>
</body>
</html>
+34
View File
@@ -0,0 +1,34 @@
{
"name": "@remake/web",
"type": "module",
"version": "3.0.0",
"author": "Vick Scarlet <vick@syaro.io>",
"scripts": {
"dev": "bunx --bun vite",
"dev:prod": "bunx --bun vite --mode production",
"build": "bunx --bun vite build",
"build:bili": "bunx --bun vite build --config vite.config.bili.ts",
"preview": "bunx --bun vite preview",
"lint": "bunx --bun eslint",
"test": "bunx --bun vitest"
},
"dependencies": {
"@remake/data": "workspace:*",
"@remake/hooks": "workspace:*",
"@remake/vitex": "workspace:*",
"@remake/thirdparty-bili-toy": "workspace:*",
"jotai": "^2.20.2",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-hot-toast": "^2.6.0"
},
"devDependencies": {
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"@vitejs/plugin-react": "^6.0.5",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.1.1",
"vite": "^8.1.3",
"vitest": "^4.1.10"
}
}
+64
View File
@@ -0,0 +1,64 @@
.screen {
min-width: 32rem;
max-width: min(calc(100dvw - 2rem), 48rem);
position: relative;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
> * { width: 100%; }
> .section {
display: flex;
flex-direction: column;
gap: calc(0.5rem * var(--ratio));
width: 100%;
padding: 0;
box-sizing: border-box;
> .title {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
font-weight: bold;
width: 100%;
height: calc(2rem * var(--ratio));
color: var(--base-text-color);
border: 0.0625rem solid var(--base-text-color);
border-bottom: 0.1875rem solid var(--color-primary);
box-sizing: border-box;
user-select: none;
}
}
> .controls {
display: flex;
flex-direction: row;
justify-content: center;
gap: 0.5rem;
width: 100%;
> button { flex: 1 0; }
}
&.loading {
display: flex;
justify-content: center;
align-items: center;
> * { width: auto; }
animation: pulse 1.8s ease-in-out infinite;
}
}
.saving {
position: fixed;
bottom: 0;
left: 0;
opacity: 0;
user-select: none;
&.active {
animation: pulse 1.8s ease-in-out infinite;
}
}
@keyframes pulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
+46
View File
@@ -0,0 +1,46 @@
import { useEffect } from 'react'
import { useInit, useWatcher } from '@/hooks/storage'
import { useStep, Step } from '@remake/hooks'
import ToastContainer from '@/toast'
import Home from '@/containers/Home'
import Mode from '@/containers/Mode'
import Chara from '@/containers/Chara'
import Pick from '@/containers/Pick'
import Alloc from '@/containers/Alloc'
import Play from '@/containers/Play'
import Summary from '@/containers/Summary'
import Achv from '@/containers/Achv'
import Thanks from '@/containers/Thanks'
import './Game.css'
export function Container() {
/* prettier-ignore */
switch (useStep()) {
case Step.Idle: return <Home />
case Step.Mode: return <Mode />
case Step.Chara: return <Chara />
case Step.Pick: return <Pick />
case Step.Alloc: return <Alloc />
case Step.Play: return <Play />
case Step.Summary: return <Summary />
case Step.Achv: return <Achv />
case Step.Thanks: return <Thanks />
default: return null
}
}
/* prettier-ignore */
const Loading = () => <div className="screen loading"><h2>...</h2></div>
const Saving = ({ active }: { active: boolean }) => (
<div className={`saving ${active ? 'active' : ''}`}>...</div>
)
/* prettier-ignore */
export function Game() {
const [inited, init] = useInit()
const saving = useWatcher()
useEffect(() => { if (!inited) init() }, [inited, init])
if (!inited) return <Loading />
return <><Container /><ToastContainer /><Saving active={saving} /></>
}
export default Game
+31
View File
@@ -0,0 +1,31 @@
.achievement-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 calc(0.5rem * var(--ratio-font));
height: calc(2rem * var(--ratio));
gap: calc(1rem * var(--ratio-font));
font-size: calc(1rem * var(--ratio-font));
color: var(--grade-color);
background: var(--background, var(--base-background-color));
cursor: pointer;
user-select: none;
border: 0.0625rem solid var(--grade-color);
box-sizing: border-box;
transition: all 0.2s ease-in-out;
svg {
fill: var(--grade-color);
transition: fill 0.2s ease-in-out;
stroke: none;
width: calc(1.2rem * var(--ratio-font));
height: calc(1.2rem * var(--ratio-font));
}
.name { font-weight: bold; }
.description {
font-size: calc(0.85rem * var(--ratio-font));
user-select: none;
&::before { content: '['; margin-right: 0.125rem; }
&::after { content: ']'; margin-left: 0.125rem; }
&::before, &::after { color: var(--grade-color); }
}
}
+33
View File
@@ -0,0 +1,33 @@
import { type Achievement as A, achievement } from '@remake/data'
import './Achievement.css'
const Icon = () => (
<svg
width="800px"
height="800px"
viewBox="0 0 6.3500002 6.3500002"
fill="currentColor"
>
<g>
<path d="m 2.5032059,-0.0095735 c -0.043217,-6.35e-4 -0.0909,0.00531 -0.1493441,0.024289 C 2.2369795,0.0525379 2.1848433,0.1094109 2.1259683,0.1650936 2.0670958,0.2207129 2.0130256,0.2808305 1.9606037,0.3407933 1.8557572,0.4607694 1.7291409,0.5919178 1.7440793,0.5810887 1.7590282,0.5703466 1.5965582,0.6493864 1.4500399,0.7118302 1.3767847,0.7430018 1.3005529,0.7771219 1.2293826,0.8156998 1.1582044,0.8544035 1.0898255,0.885282 1.0175096,0.9846817 0.9451937,1.0840436 0.9358089,1.159797 0.9208732,1.2394466 0.9059507,1.319096 0.8985953,1.400403 0.8914171,1.479742 0.8770502,1.6384195 0.8500416,1.8161314 0.8557619,1.7985856 c 0.00572,-0.017558 -0.07988,0.1429176 -0.1617478,0.2795696 -0.040934,0.068351 -0.082447,0.141311 -0.1173056,0.214457 -0.034859,0.073146 -0.069638,0.1380463 -0.069763,0.2609662 -1.217e-4,0.1229198 0.03557,0.1882605 0.070279,0.2614827 0.034711,0.073247 0.07651,0.1434605 0.1173057,0.2118733 0.081592,0.1368131 0.1663991,0.2976496 0.1607158,0.2800863 -0.00569,-0.017526 0.019526,0.1632389 0.033589,0.3219442 0.00704,0.079296 0.014684,0.1606161 0.029456,0.2402954 0.014764,0.079742 0.022966,0.1557634 0.095083,0.2552817 0.072115,0.099531 0.1412954,0.130652 0.2123916,0.1694987 0.071099,0.038784 0.1469443,0.071021 0.2201413,0.1023191 0.1463886,0.062722 0.3099884,0.1442029 0.2950712,0.1333254 -0.014922,-0.010713 0.109855,0.1175901 0.2144581,0.2377115 0.0523,0.060092 0.1060847,0.1214847 0.1648486,0.1772502 0.058759,0.055816 0.1110827,0.1117997 0.2278909,0.1498616 0.1168056,0.03815 0.190463,0.023744 0.2707852,0.013435 0.080328,-0.010432 0.1605915,-0.026186 0.2382282,-0.043925 0.1552734,-0.035351 0.3341952,-0.068207 0.3157432,-0.068213 -0.018441,-1.85e-5 0.1595093,0.03148 0.3147086,0.06718 0.077602,0.017849 0.1568874,0.035993 0.2371963,0.046509 0.08029,0.010893 0.1544188,0.024387 0.2713012,-0.013436 C 4.1130209,5.0582357 4.1651597,5.0033666 4.2240321,4.9477472 4.2829046,4.8921276 4.3395598,4.8298801 4.3919791,4.7699803 4.4968281,4.6500042 4.6208595,4.521427 4.6059211,4.5322686 4.5909722,4.543008 4.7555086,4.4639712 4.9020269,4.4015274 4.9752821,4.3703558 5.0494475,4.3357186 5.1206178,4.2971408 5.1917933,4.258437 5.2601749,4.2280628 5.3324908,4.1286758 5.4048041,4.0293137 5.4141915,3.9535603 5.4291272,3.8739109 5.4440497,3.7942615 5.4514051,3.7129545 5.4585806,3.6336155 5.4729475,3.4749377 5.4999562,3.2946422 5.4942385,3.312188 5.4885235,3.3297458 5.5741162,3.1692831 5.6559863,3.0326187 5.69692,2.9642673 5.7384331,2.8938913 5.7732919,2.8207451 5.8081508,2.7475992 5.8429303,2.6827115 5.8430546,2.5597792 5.8431763,2.4368593 5.807484,2.3715183 5.7727734,2.2982965 5.7380653,2.2250492 5.6967824,2.152252 5.6559863,2.0838392 5.5743941,1.9470137 5.4890712,1.7882568 5.4947545,1.8058201 5.500443,1.8233461 5.4752282,1.6425809 5.4611656,1.4838759 5.4541542,1.404454 5.4464812,1.3232598 5.4317096,1.2435805 5.4169458,1.163838 5.4087437,1.0878171 5.3366262,0.9882988 5.2645114,0.8887679 5.1953308,0.8581762 5.1242346,0.8193169 5.0531358,0.7805332 4.977285,0.7458378 4.9040933,0.7144138 4.7577047,0.6516914 4.5941022,0.5728705 4.6090221,0.5836723 4.6239446,0.5943853 4.499683,0.4630319 4.39508,0.3428602 4.3427798,0.282768 4.2884767,0.2219049 4.2297154,0.1661267 4.1709567,0.1103108 4.1186327,0.0543271 4.0018218,0.0162652 3.8850188,-0.0218851 3.8113668,-0.0055365 3.7310393,0.004896 3.6507118,0.0153285 3.5704478,0.0315991 3.4928111,0.0493378 3.3375377,0.0846891 3.1586212,0.1174185 3.1770679,0.1175506 3.1955094,0.1175694 3.0175559,0.0834873 2.8623566,0.0477876 2.784757,0.0299388 2.7054693,0.0138614 2.6251629,0.0033458 2.5850177,-0.002102 2.5464229,-0.0089361 2.5032059,-0.0095735 Z m 0.6810957,1.3916462 a 0.26407552,0.26514563 1.664302 0 1 0.2098067,0.1069702 l 0.3064404,0.4149617 0.5167657,0.1953371 a 0.26407552,0.26514563 1.664302 0 1 0.1209225,0.403076 L 4.0395461,2.9240974 4.0142255,3.4780688 A 0.26407552,0.26514563 1.664302 0 1 3.6721272,3.7188812 L 3.1791343,3.563335 2.6468667,3.7090625 A 0.26407552,0.26514563 1.664302 0 1 2.3109702,3.4548146 L 2.3068427,2.9364998 2.0050537,2.4734789 A 0.26407552,0.26514563 1.664302 0 1 2.1404463,2.0760872 L 2.6324046,1.9133065 2.9781198,1.4807747 a 0.26407552,0.26514563 1.664302 0 1 0.1865524,-0.098185 0.26407552,0.26514563 1.664302 0 1 0.019632,-5.292e-4 z" />
<path d="m 1.32395,4.6940153 v 0.8454266 c 0,0.22689 0.085796,0.4353634 0.2320264,0.5999633 0.073102,0.0823 0.1640284,0.155757 0.2831888,0.1953369 0.1191578,0.0396 0.2710842,0.033444 0.396875,-0.029456 L 3.1755175,5.8365814 4.1149947,6.3052865 a 0.26460996,0.26460996 0 0 0 0.1188561,0.027389 c 0.4351602,0 0.7932315,-0.3580833 0.7932315,-0.7932332 V 4.6976326 c -0.1063255,0.047962 -0.2399666,0.1090639 -0.2284096,0.1007693 0.01696,-0.0123 -0.1239096,0.1336209 -0.2428795,0.2697509 -0.059468,0.06796 -0.1238859,0.1384379 -0.1906852,0.2015381 -0.066799,0.0631 -0.1257724,0.1255652 -0.2583815,0.168465 -0.1326198,0.0429 -0.2168922,0.027386 -0.3079909,0.014986 -0.09112,-0.011999 -0.1806178,-0.03241 -0.2687188,-0.05271 -0.1760908,-0.0405 -0.3779838,-0.075984 -0.3570843,-0.075964 0.020929,0 -0.1824646,0.036898 -0.3586348,0.076998 -0.08809,0.0201 -0.1791679,0.038326 -0.2702666,0.050126 -0.09113,0.0117 -0.1744398,0.027797 -0.306959,-0.015502 C 2.1045317,5.3929098 2.0448735,5.3293743 1.9781747,5.2660743 1.9114944,5.2028043 1.8504047,5.133253 1.7911063,5.0650529 1.6724248,4.9287629 1.5313251,4.7831021 1.5482267,4.795302 1.5595777,4.803572 1.4276667,4.741909 1.32395,4.6940153 Z" />
</g>
</svg>
)
export interface AchievementProps {
id: A['id']
}
export function Achievement({ id }: AchievementProps) {
const ach = achievement.get(id)
if (!ach) return null
const grade = ach?.grade ?? 0
return (
<div className={`achievement-item grade-${grade}`}>
<Icon />
<span className="name">{ach.name}</span>
<span className="description">{ach.description}</span>
</div>
)
}
export default Achievement
+12
View File
@@ -0,0 +1,12 @@
button.github-btn {
svg {
width: 100%;
height: 100%;
fill: var(--style-color);
transition: fill 0.2s ease-in-out;
}
@media (hover: hover) {
&:hover svg { fill: var(--style-background); }
}
&:active svg{ fill: var(--style-background); }
}
+23
View File
@@ -0,0 +1,23 @@
import { toastMsg } from '@/toast'
import './Github.css'
const repo = 'https://github.com/VickScarlet/remake'
export function Github() {
const handleClick = () => {
if (import.meta.env.VITE_CHANNEL === 'bili') toastMsg(repo, 'github')
else window.open(repo)
}
return (
<button className="github-btn" onClick={handleClick}>
<svg
fill="currentColor"
width="800px"
height="800px"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9a127.5 127.5 0 0 1 38.1 91v112.5c.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z" />
</svg>
</button>
)
}
export default Github
+10
View File
@@ -0,0 +1,10 @@
.message-item {
display: flex;
justify-content: center;
align-items: center;
height: 2rem;
border: 0.0625rem solid var(--base-text-color);
color: var(--base-text-color);
background: var(--base-background-color);
padding: 0 1rem;
}
+10
View File
@@ -0,0 +1,10 @@
import './Message.css'
export interface MsgToastProps {
message: React.ReactNode
}
export function MsgToast({ message }: MsgToastProps) {
return <div className="message-item">{message}</div>
}
export default MsgToast
+50
View File
@@ -0,0 +1,50 @@
ul.replaced {
display: flex;
flex-direction: column;
gap: 0.5rem;
li {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 0;
.talent {
width: 100%;
}
}
li:not(:first-child) {
position: relative;
&::before {
content: '';
position: absolute;
display: block;
box-sizing: border-box;
border: 0.0625rem solid var(--base-text-color);
border-left: none;
right: 0;
top: -1rem;
margin-right: -1rem;
width: 0.625rem;
height: 1.5rem;
}
&::after {
content: '';
position: absolute;
display: block;
box-sizing: border-box;
border: 0.0625rem solid var(--base-text-color);
border-right: none;
border-bottom: none;
right: 0;
top: 0.5rem;
margin-right: -0.6875rem;
margin-top: -0.03125rem;
width: 0.375rem;
height: 0.375rem;
transform: rotate(-45deg);
transform-origin: 0 0;
}
}
}
+28
View File
@@ -0,0 +1,28 @@
import { type Talent as T, talents } from '@remake/data'
import Talent from './Talent'
import './Replaced.css'
export interface TalentProps {
id: T['id']
chains?: T['id'][]
}
export function Replaced({ id, chains }: TalentProps) {
return (
<ul className="replaced">
<li>
<Talent id={id} selected={true} />
</li>
{chains?.map(id => {
const talent = talents.get(id)
if (!talent) return null
return (
<li key={id}>
<Talent id={id} selected={false} />
</li>
)
})}
</ul>
)
}
export default Replaced
+62
View File
@@ -0,0 +1,62 @@
.talent {
--color: var(--grade-color);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 1rem;
height: calc(2rem * var(--ratio));
font-size: calc(1rem * var(--ratio-font));
gap: 1rem;
color: var(--color);
background: var(--background, var(--base-background-color));
cursor: pointer;
user-select: none;
border: 0.0625rem solid var(--grade-color);
box-sizing: border-box;
transition: all 0.2s ease-in-out;
&.selected {
--color: var(--base-background-color);
--background: var(--grade-color);
@media (hover: hover) {
&:hover { background: oklch(from var(--grade-color) calc(l + 0.1) c h); }
}
&:active {
background: oklch(from var(--grade-color) calc(l + 0.1) c h);
transition: none;
}
}
@media (hover: hover) {
&:hover { background: oklch(from var(--grade-color) calc(l + 0.5) c h); }
}
&:active {
background: oklch(from var(--grade-color) calc(l + 0.5) c h);
transition: none;
}
.talent-name { font-weight: bold; }
.talent-description {
color: oklch(from var(--color) calc(l + 0.1) c h);
font-size: calc(0.85rem * var(--ratio-font));
user-select: none;
&::before { content: '['; margin-right: 0.125rem; }
&::after { content: ']'; margin-left: 0.125rem; }
&::before, &::after { color: var(--color); }
}
}
html[data-theme='dark'] {
.talent{
@media (hover: hover) {
&:hover { background: oklch(from var(--grade-color) calc(l - 0.5) c h); }
&.selected:hover { background: oklch(from var(--grade-color) calc(l - 0.1) c h); }
}
&:active {
background: oklch(from var(--grade-color) calc(l - 0.5) c h);
transition: none;
}
&.selected:active {
background: oklch(from var(--grade-color) calc(l - 0.1) c h);
transition: none;
}
.talent-description { color: oklch(from var(--color) calc(l - 0.2) c h); }
}
}
+20
View File
@@ -0,0 +1,20 @@
import { type Talent as T, talents } from '@remake/data'
import './Talent.css'
export interface TalentProps {
id: T['id']
selected?: boolean
}
export function Talent({ id, selected }: TalentProps) {
const talent = talents.get(id)
if (!talent) return null
const grade = talent?.grade ?? 0
return (
<div className={`talent grade-${grade} ${selected ? 'selected' : ''}`}>
<span className="talent-name">{talent.name}</span>
<span className="talent-description">{talent.description}</span>
</div>
)
}
export default Talent
+37
View File
@@ -0,0 +1,37 @@
import { useState, useEffect, useRef } from 'react'
export interface Props {
text: string
className: string
}
export function TextSvg({ text, className }: Readonly<Props>) {
const [height, setHeight] = useState(0)
const [width, setWidth] = useState(0)
const textRef = useRef<SVGTextElement>(null)
useEffect(() => {
if (!textRef.current) return
const { width, height } = textRef.current.getBBox()
setWidth(width)
setHeight(height)
}, [text, className, textRef])
return (
<svg
className={className}
fill="currentColor"
viewBox={`0 0 ${width} ${height}`}
>
<text
ref={textRef}
fontSize={10}
style={{
transform: 'translate(50%,50%)',
textAnchor: 'middle',
dominantBaseline: 'central',
}}
>
{text}
</text>
</svg>
)
}
export default TextSvg
+37
View File
@@ -0,0 +1,37 @@
.theme-svg {
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
transform-origin: center;
.sun-center, .sun-beams, .mask-cutter-group {
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1);
transform-origin: center;
}
.mask-cutter-group { transform: translate(0.75rem, -0.75rem); }
.sun-center {
transition: fill 0.2s ease-in-out;
fill: var(--style-color);
}
.sun-beams {
transition: stroke 0.2s ease-in-out;
stroke: var(--style-color);
}
}
button.theme-toggle-btn {
@media (hover: hover) {
&:hover .theme-svg{
.sun-center { fill: var(--style-background); }
.sun-beams { stroke: var(--style-background); }
}
}
&:active .theme-svg{
.sun-center { fill: var(--style-background); }
.sun-beams { stroke: var(--style-background); }
}
}
html[data-theme='dark'] {
.mask-cutter-group { transform: translate(0, 0); }
.theme-svg { transform: rotate(375deg); }
.sun-center { transform: scale(1.5); }
.sun-beams { opacity: 0; transform: scale(0.6); }
}
+85
View File
@@ -0,0 +1,85 @@
import { useState, useEffect } from 'react'
import './ThemeToggle.css'
export const updateThemeColor = () => {
let metas = document.querySelectorAll('meta[name="theme-color"]')
if (!metas.length) return
const element = document.body
const end = Date.now() + 300
const update = () => {
const { backgroundColor } = getComputedStyle(element)
metas.forEach(meta => meta.setAttribute('content', backgroundColor))
if (Date.now() < end) requestAnimationFrame(update)
}
requestAnimationFrame(update)
}
export function ThemeToggle() {
const [isDark, setIsDark] = useState(() => {
const storedTheme = localStorage.getItem('theme')
if (storedTheme === 'dark') return true
if (storedTheme === 'light') return false
return matchMedia('(prefers-color-scheme: dark)').matches
})
useEffect(() => {
document.documentElement.dataset.theme = isDark ? 'dark' : 'light'
updateThemeColor()
localStorage.setItem('theme', isDark ? 'dark' : 'light')
}, [isDark])
return (
<button
className="theme-toggle-btn"
onClick={() => setIsDark(!isDark)}
aria-label="Toggle theme"
>
<svg
className="theme-svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<defs>
<mask id="ios-fail-proof-mask">
<rect
x="0"
y="0"
width="24"
height="24"
fill="#ffffff"
/>
<g className="mask-cutter-group">
<circle cx="18" cy="5" r="7" fill="#000000" />
</g>
</mask>
</defs>
<circle
className="sun-center"
cx="12"
cy="12"
r="5"
mask="url(#ios-fail-proof-mask)"
fill="currentColor"
/>
<g className="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
)
}
export default ThemeToggle
+151
View File
@@ -0,0 +1,151 @@
import type { Config } from '@remake/hooks'
export const dev = {} as Record<string, any>
if (import.meta.env.MODE === 'development') {
new URLSearchParams(window.location.search)
.entries()
.forEach(([key, value]) => {
if (key !== 'locked') return (dev[key] = Number(value))
dev[key] = value.split(',').map(v => Number(v))
})
}
//
export const Features = dev.features ?? 1
// 自动模式自动点击间隔
export const AutoInterval = dev.interval ?? 500
// 可锁定的天赋数量
export const LockLimit = dev.lock ?? 1
// 名人模式重开次数限制
export const ModeLimit = dev.mode ?? 10
// 天赋选择数量限制
export const PickMax = dev.max ?? 3
// 天赋选择最少数量限制
export const PickMin = dev.min ?? 3
// 基础属性点
export const BasePoints = dev.points ?? 20
// 单项属性点最大限制
export const AllocLimit = dev.allocate ?? 10
// 默认初始快乐
export const DefaultSpirit = dev.spirit ?? 5
// 天赋抽取个数
export const PullCount = dev.pull ?? 10
// 名人模式抽取个数
export const CharacterPullCount = dev.chara ?? 3
// 名人模式抽取权重切线
export const CharacterWeightKnife = dev.knife ?? 10
// 唯一角色生成抽卡次数限制
export const UniqueLimit = dev.unique ?? 10
// 天赋抽取基础概率
export const PullRateBase: Config['pull']['rate']['base'] = new Map([
[0, 889],
[1, 100],
[2, 10],
[3, 1],
])
// 属性评级线
export const JudgeLineMap = {
age: [0, 1, 10, 18, 40, 60, 70, 80, 90, 95, 100, 500],
summary: [0, 41, 50, 60, 80, 100, 110, 120],
times: [0, 10, 30, 50, 70, 100],
achievements: [0, 10, 30, 50, 70, 100],
charm: [0, 1, 2, 4, 7, 9, 11],
money: [0, 1, 2, 4, 7, 9, 11],
spirit: [0, 1, 2, 4, 7, 9, 11],
intelligence: [0, 1, 2, 4, 7, 9, 11, 21, 131, 501],
strength: [0, 1, 2, 4, 7, 9, 11, 21, 101, 401, 1001, 2001],
talent: [0, 30, 60, 90],
event: [0, 20, 40, 60],
}
// 可评级属性
export type Judges = keyof typeof JudgeLineMap
// 获取属性评级
export function judge(key: Judges, value: number) {
const arr = JudgeLineMap[key]
for (let i = arr.length - 1; i >= 0; i--) {
if (value >= arr[i]!) return i
}
return 0
}
// 属性稀有度等级线
export const JudgeGradeMap = {
age: [0, 5, 7, 9],
summary: [0, 4, 5, 6],
times: [0, 1, 3, 5],
achievements: [0, 1, 3, 5],
charm: [0, 4, 5, 6],
money: [0, 4, 5, 6],
spirit: [0, 4, 5, 6],
intelligence: [0, 4, 5, 6],
strength: [0, 4, 5, 6],
talent: [0, 1, 2, 3],
event: [0, 1, 2, 3],
}
// 获取属性稀有度
export function judgeGrade(key: Judges, level: number) {
const arr = JudgeGradeMap[key]
for (let i = arr.length - 1; i >= 0; i--) {
if (level >= arr[i]!) return i
}
return 0
}
// 根据属性值获取属性稀有度
export function judgeGradeByValue(key: Judges, value: number) {
const level = judge(key, value)
return judgeGrade(key, level)
}
// 天赋抽取概率加成
export const PullRateAdditions: Config['pull']['rate']['additions'] = {
times: value => {
const level = judge('times', value)
return new Map([[2, { mode: 'multiply', value: level + 1 } as const]])
},
achievements: value => {
const level = judge('achievements', value.size)
return new Map([[3, { mode: 'multiply', value: level + 1 } as const]])
},
}
// 权重生成器
function wg(s: number, e: number) {
const length = e - s + 1
return Array.from(
{ length },
(_, i) => [s + i, Math.min(i + 1, length - i)] as const,
)
}
// 唯一角色属性分配权重
export const UniquePropWeight = wg(0, 10)
// 唯一角色天赋个数权重
export const UniqueTalentWeight = wg(1, 5)
export const config: Config = {
features: Features,
lock: LockLimit,
mode: ModeLimit,
max: PickMax,
min: PickMin,
points: BasePoints,
allocate: AllocLimit,
spirit: DefaultSpirit,
pull: {
count: PullCount,
rate: {
base: PullRateBase,
additions: PullRateAdditions,
},
},
chara: {
count: CharacterPullCount,
knife: CharacterWeightKnife,
},
unique: {
limit: UniqueLimit,
config: {
prop: UniquePropWeight,
talent: UniqueTalentWeight,
},
},
}
export default config
+119
View File
@@ -0,0 +1,119 @@
.screen.achv {
height: calc(100dvh - 2rem);
width: min(calc(100dvw - 2rem), 48rem);
gap: 1rem;
> .stats {
> .details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
> div {
display: flex;
flex-direction: column;
align-items: center;
border: 0.0625rem solid var(--grade-color);
color: var(--grade-color);
box-sizing: border-box;
position: relative;
transform-style: preserve-3d;
user-select: none;
> span {
display: flex;
align-items: center;
justify-content: center;
}
> .title {
font-weight: bold;
font-size: 1.5rem;
height: 2.5rem;
}
> .value {
height: 2rem;
}
}
> .prg {
padding-bottom: 0.1875rem;
&::before,
&::after {
content: '';
display: block;
position: absolute;
height: 0.1875rem;
left: 0;
bottom: 0;
transform: translateZ(-1px);
}
&::after {
width: var(--progress, 0%);
background: var(--grade-color);
transition: width 0.2s ease-in-out;
}
&::before {
width: 100%;
border-top: 0.0625rem solid var(--grade-color);
box-sizing: border-box;
}
}
}
}
> .achievement-list {
flex: 1 0;
width: 100%;
> ul {
flex: 1 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
gap: 0.5rem;
box-sizing: border-box;
> li.achievement {
margin-right: 0.0625rem;
display: flex;
flex-direction: column;
align-items: center;
color: var(--grade-color);
border: 0.0625rem solid var(--grade-color);
padding: 0 0.0625rem;
box-sizing: border-box;
transform-style: preserve-3d;
position: relative;
user-select: none;
>.name {
font-weight: bold;
font-size: 1.2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
}
>.description {
height: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
}
&::before{
content: '';
display: block;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
padding: 0.0625rem;
margin: -0.0625rem;
transform: translateZ(1px);
background: var(--base-background-color);
opacity: 0.5;
}
&.colled {
&::before{
transform: translateZ(-1px);
background: var(--grade-color);
opacity: 0.15;
}
}
}
}
}
}
+86
View File
@@ -0,0 +1,86 @@
import { useAchv, useGoHome } from '@remake/hooks'
import { judge, judgeGrade, judgeGradeByValue } from '@/config'
import { rates } from '@/display'
import { achievements, type Achievement } from '@remake/data'
import { toastMsg } from '@/toast'
import './Achv.css'
interface AchievementProps {
id: Achievement['id']
colled: boolean
}
export function AchievementItem({ id, colled }: AchievementProps) {
const { name, grade, description, hide } = achievements.get(id)!
return (
<li className={`achievement grade-${grade} ${colled ? 'colled' : ''}`}>
<span className="name">{hide && !colled ? '???' : name}</span>
<span className="description">
{hide && !colled ? '???' : description}
</span>
</li>
)
}
export function Achv() {
const goHome = useGoHome()
const showRank = () => toastMsg('别卷了,没有排行榜', 'achv-rank')
const [stats, sorted] = useAchv()
const jtimes = judge('times', stats.times)
const gtimes = judgeGrade('times', jtimes)
const jachv = judge('achievements', stats.achv)
const gachv = judgeGrade('achievements', jachv)
const gevent = judgeGradeByValue('event', stats.event)
const revent = stats.event.toFixed(2) + '%'
const gtalent = judgeGradeByValue('talent', stats.talent)
const rtalent = stats.talent.toFixed(2) + '%'
return (
<div className="screen achv">
<div className="controls">
<button className="secondary" onClick={showRank}>
</button>
<button className="primary" onClick={goHome}>
</button>
</div>
<div className="section stats">
<div className="title"></div>
<div className="details">
<div className={`grade-${gtimes}`}>
<span className="title">{stats.times}</span>
<span className="value">{rates.times[jtimes]}</span>
</div>
<div className={`grade-${gachv}`}>
<span className="title">{stats.achv}</span>
<span className="value">
{rates.achievement[jachv]}
</span>
</div>
<div
className={`prg grade-${gevent}`}
style={{ '--progress': revent } as React.CSSProperties}
>
<span className="title"></span>
<span className="value">{revent}</span>
</div>
<div
className={`prg grade-${gtalent}`}
style={{ '--progress': rtalent } as React.CSSProperties}
>
<span className="title"></span>
<span className="value">{rtalent}</span>
</div>
</div>
</div>
<div className="section achievement-list hide-scrollbar">
<div className="title"></div>
<ul className="details hide-scrollbar">
{sorted.map(({ id, colled }) => (
<AchievementItem key={id} id={id} colled={colled} />
))}
</ul>
</div>
</div>
)
}
export default Achv
+123
View File
@@ -0,0 +1,123 @@
.screen.point-allocation {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 1rem;
width: 100%;
.allocation-input {
display: flex;
gap: 0;
height: calc(2rem * var(--ratio));
border: 0.0625rem solid var(--grade-color);
color: var(--grade-color);
box-sizing: border-box;
width: 100%;
transition: all 0.2s ease-in-out;
button {
--style-color: var(--grade-color);
flex: 1 0;
padding: 0;
margin: 0;
height: 100%;
border: none;
text-align: center;
}
input {
flex: 1 0;
height: 100%;
width: 2rem;
font-weight: bold;
color: var(--grade-color);
transition: all 0.2s ease-in-out;
}
}
.points-detail {
min-width: 100%;
color: var(--color-primary);
display: flex;
position: absolute;
flex-direction: column;
white-space: nowrap;
background: var(--base-background-color);
border: 0.0625rem solid var(--color-primary);
box-sizing: border-box;
top: 0;
left: 0;
transform: translateY(-100%);
margin: 0;
font-size: calc(1rem * var(--ratio-font));
li {
display: flex;
justify-content: space-between;
align-items: center;
height: calc(2rem * var(--ratio));
padding: 0 0.5rem;
gap: 0.5rem;
}
li:not(:first-child) {
border-top: 0.0625rem solid var(--color-primary);
}
}
> ul.alloc {
display: flex;
flex-direction: row;
justify-content: center;
gap: 0.5rem;
padding: 0;
margin: 0;
list-style: none;
width: 100%;
font-size: calc(1rem * var(--ratio-font));
> li {
flex: 1 0;
display: flex;
position: relative;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0;
> span {
width: 100%;
height: calc(2rem * var(--ratio));
color: var(--grade-color);
background: var(--base-background-color);
border: 0.0625rem solid var(--grade-color);
border-bottom: none;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
font-weight: bold;
transition: all 0.2s ease-in-out;
}
> span.name {
color: var(--base-background-color);
background: var(--grade-color);
}
}
> li.left {
--left-color: var(--color-error);
&.left-0 { --left-color: var(--color-primary); }
> span {
background: var(--left-color);
border: 0.0625rem solid var(--left-color);
}
> button {
--style-color: var(--left-color);
flex: 1;
font-size: calc(1rem * var(--ratio-font));
height: calc(2rem * var(--ratio));
width: 6rem;
padding: 0;
}
}
}
> ul.talent-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
li { width: 100%; }
}
}
+134
View File
@@ -0,0 +1,134 @@
import { useState } from 'react'
import { useAllocator, usePointRandomizer, useLeftPoints } from '@remake/hooks'
import { usePicked, useReplaced, useStart, useIsClassic } from '@remake/hooks'
import type { AdditionalPoint } from '@remake/hooks'
import { properties } from '@/display'
import { keys } from '@remake/vitex'
import { judgeGradeByValue } from '@/config'
import { talents } from '@remake/data'
import { toastAchvs, toastMsg } from '@/toast'
import Replaced from '@/components/Replaced'
import './Alloc.css'
interface AllocInputProps {
point: number
onChange: (point: number) => void
}
function AllocInput(props: AllocInputProps) {
return (
<div className="allocation-input">
<button onClick={() => props.onChange(props.point - 1)}></button>
<input
className="font-mono"
type="number"
value={props.point}
onChange={e => props.onChange(Number(e.target.value))}
/>
<button onClick={() => props.onChange(props.point + 1)}>+</button>
</div>
)
}
interface PointsDetailProps {
base: number
source: AdditionalPoint[]
fixed?: boolean
}
function PointsDetail({ base, source, fixed }: PointsDetailProps) {
return (
<ul className="points-detail">
<li>
<span>{fixed ? '固定' : '基础'}</span>
<span className="font-mono">{base}</span>
</li>
{source.map(({ talent, points }) => (
<li key={talent}>
<span>{talents.get(talent)?.name ?? talent}</span>
<span className="font-mono">{points}</span>
</li>
))}
</ul>
)
}
export function Alloc() {
const isClassic = useIsClassic()
const picked = usePicked()
const {
talents: { chains },
additionalPoints: { source },
} = useReplaced()
const { base, left } = useLeftPoints()
const [{ alloc, final, base: ba }, allocator] = useAllocator()
const random = usePointRandomizer()
const start = useStart()
const [showDetail, setShowDetail] = useState(false)
const handleNext = () => {
if (left != 0) return toastMsg('还有剩余点数未分配', 'alloc-toast')
const achievements = start()
toastAchvs(achievements)
}
return (
<div className="screen point-allocation">
<ul className="talent-list">
{Array.from(picked, id => (
<li key={id}>
<Replaced id={id} chains={chains.get(id)} />
</li>
))}
</ul>
<ul className={`alloc ${isClassic ? 'classic' : 'modify'}`}>
<li className={`left left-${left}`}>
<span className="name"></span>
<button
className="font-mono"
onClick={() => setShowDetail(!showDetail)}
>
{left}
</button>
{showDetail && (
<PointsDetail
base={base}
source={source}
fixed={!isClassic}
/>
)}
</li>
{keys(alloc).map(key => (
<li
key={key}
className={`property grade-${judgeGradeByValue(key, final[key])}`}
>
<span className="name">
{properties[key]}
{!isClassic && (
<span className="font-mono">[{ba[key]}]</span>
)}
</span>
{!isClassic && (
<span className="font-mono">{final[key]}</span>
)}
<AllocInput
point={alloc[key]}
onChange={value => allocator(key, value)}
/>
</li>
))}
</ul>
<div className="controls">
<button className="secondary" onClick={() => random()}>
</button>
<button
className={left ? 'error' : 'primary'}
onClick={handleNext}
>
</button>
</div>
</div>
)
}
export default Alloc
+91
View File
@@ -0,0 +1,91 @@
.screen.chara {
gap: 1rem;
> .chara-list {
display: flex;
flex-direction: column;
gap: calc(0.5rem * var(--ratio));
> .character {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 0.0625rem solid var(--base-text-color);
box-sizing: border-box;
cursor: pointer;
> .generator > ul > li { text-align: center; }
> .name {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: calc(1rem * var(--ratio-font));
height: calc(2rem * var(--ratio));
}
&.selected {
border: 0.0625rem solid var(--color-primary);
> .name {
background-color: var(--color-primary);
color: var(--base-background-color);
}
> .chara-details {
height: auto;
padding: 0.5rem;
}
}
}
}
.chara-details {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
border-top: 0.0625rem solid var(--base-text-color);
box-sizing: border-box;
height: 0;
padding: 0 0.5rem;
overflow-y: hidden;
transition: all 0.3s ease-in-out;
interpolate-size: allow-keywords;
> ul.talent-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
> ul.properties {
display: flex;
flex-direction: row;
gap: 0.5rem;
width: 100%;
font-weight: bold;
> li {
display: flex;
flex-direction: column;
flex: 1 0;
justify-content: space-between;
text-align: center;
min-width: 6rem;
user-select: none;
color: var(--grade-color);
span {
display: flex;
justify-content: center;
align-items: center;
border: 0.0625rem solid var(--grade-color);
box-sizing: border-box;
height: 2rem;
&:first-child {
border-bottom: none;
}
}
.name {
color: var(--base-background-color);
background-color: var(--grade-color);
}
&.charm { order: 1 }
&.intelligence { order: 2 }
&.strength { order: 3 }
&.money { order: 4 }
}
}
}
}
+132
View File
@@ -0,0 +1,132 @@
import { useCharaPuller, useCharaPicker, useCharaSubmit } from '@remake/hooks'
import { useUnique, useUniqueGenerator, convertProps } from '@remake/hooks'
import type { BaseChara } from '@remake/hooks'
import { judgeGradeByValue } from '@/config'
import { characters } from '@remake/data'
import { properties } from '@/display'
import { keys } from '@remake/vitex'
import { toastMsg } from '@/toast'
import Talent from '@/components/Talent'
import './Chara.css'
function Details({ detail }: { detail: BaseChara }) {
const props = convertProps(detail.property)
return (
<div className="chara-details">
<ul className="properties">
{keys(props).map(key => (
<li
key={key}
className={`property ${key} grade-${judgeGradeByValue(key, props[key])}`}
>
<span>{properties[key]}</span>
<span className="font-mono">{props[key]}</span>
</li>
))}
</ul>
<ul className="talent-list">
{detail.talent.map(id => (
<li key={id}>
<Talent id={id} />
</li>
))}
</ul>
</div>
)
}
interface UniqueProps {
selected?: boolean
picker?: () => void
}
function Unique({ selected, picker }: UniqueProps) {
const [unique, generator] = useUniqueGenerator()
return (
<li
className={`character ${selected ? 'selected' : ''}`}
onClick={picker}
>
<span className="name"></span>
{unique && <Details detail={unique} />}
{!unique && (
<div className="chara-details generator">
<ul>
<li>6000</li>
<li> </li>
<li></li>
</ul>
<button
className="primary focus"
onClick={() => generator()}
>
</button>
</div>
)}
</li>
)
}
interface CharacterProps {
id: number
selected?: boolean
picker?: (id: number) => void
}
function Character({ id, selected, picker }: CharacterProps) {
const character = characters.get(id)!
return (
<li
className={`character ${selected ? 'selected' : ''}`}
onClick={() => picker?.(id)}
>
<span className="name">{character.name}</span>
<Details detail={character} />
</li>
)
}
export function Chara() {
const u = useUnique()
const [{ unique, characters }, puller] = useCharaPuller()
const [picked, picker] = useCharaPicker()
const submit = useCharaSubmit()
const handleSubmit = () => {
if (ready) return submit(picked)
if (!picked) toastMsg('请先选择角色', 'chara-toast-pick')
else toastMsg('请先生成唯一角色', 'chara-toast-unique')
}
const ready = picked && (picked.type === 'unique' ? !!u : true)
return (
<div className="screen chara">
<ul className="chara-list">
{unique && (
<Unique
selected={picked?.type === 'unique'}
picker={() => picker.unique()}
/>
)}
{characters.map(id => (
<Character
key={id}
id={id}
selected={picked?.id === id}
picker={id => picker.chara(id)}
/>
))}
</ul>
<div className="controls">
<button className="secondary" onClick={puller}>
</button>
<button
className={ready ? 'primary' : 'error'}
onClick={handleSubmit}
>
</button>
</div>
</div>
)
}
export default Chara
+34
View File
@@ -0,0 +1,34 @@
.screen.home {
gap: 5rem;
.title {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.main { font-weight: bold; }
}
> .actions {
position: fixed;
bottom: 1rem;
right: 1rem;
width: auto;
display: flex;
gap: 1rem;
>button {
width: 2.5rem;
height: 2.5rem;
padding: 0.25rem;
}
}
>.controls {
flex-direction: column;
> div {
flex: 1 0;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
> button { flex: 1 0; }
}
}
}
+41
View File
@@ -0,0 +1,41 @@
import { useRemake, useFeatures, useGoAchv, useGoThanks } from '@remake/hooks'
import { TextSvg } from '@/components/TextSvg'
import ThemeToggle from '@/components/ThemeToggle'
import Github from '@/components/Github'
import './Home.css'
export default function Home() {
const remake = useRemake()
const features = useFeatures()
const goAchv = useGoAchv()
const goThanks = useGoThanks()
return (
<div className="screen home">
<div className="title">
<TextSvg text="人生重开模拟器" className="main" />
<TextSvg text="这垃圾人生一秒也不想待了" className="sub" />
</div>
<div className="controls">
<div>
<button className="primary focus" onClick={remake}>
</button>
</div>
{features && (
<div>
<button className="secondary" onClick={goAchv}>
</button>
<button className="secondary" onClick={goThanks}>
</button>
</div>
)}
</div>
<div className="actions">
{features && <Github />}
<ThemeToggle />
</div>
</div>
)
}
+23
View File
@@ -0,0 +1,23 @@
.screen.mode {
gap: 1rem;
button {
display: flex;
flex-direction: column;
height: 10rem;
gap: 0.5rem;
.name {
font-size: 3.5rem;
font-weight: bold;
}
.description {
display: flex;
flex-direction: row;
gap: 1rem;
span {
font-size: 1rem;
&::before { content: '['; }
&::after { content: ']'; }
}
}
}
}
+26
View File
@@ -0,0 +1,26 @@
import { PullCount } from '@/config'
import { useModeChoose } from '@remake/hooks'
import './Mode.css'
export function Mode() {
const [Mode, choose] = useModeChoose()
return (
<div className="screen mode">
<button onClick={() => choose(Mode.Classic)}>
<div className="name"></div>
<div className="description">
<span>{PullCount} </span>
<span></span>
</div>
</button>
<button onClick={() => choose(Mode.Celebrity)}>
<div className="name"></div>
<div className="description">
<span></span>
</div>
</button>
</div>
)
}
export default Mode
+22
View File
@@ -0,0 +1,22 @@
.screen.talent-pick {
display: flex;
flex-direction: column;
justify-content: center;
gap: 1rem;
width: 100%;
ul.talent-list {
display: flex;
flex-direction: column;
gap: calc(0.5rem * var(--ratio));
li {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 0;
.talent {
width: 100%;
}
}
}
}
+52
View File
@@ -0,0 +1,52 @@
import { useTalentPuller, useTalentPicker } from '@remake/hooks'
import { useTalentSubmit, useSubmitIsEnable } from '@remake/hooks'
import { PullCount, dev } from '@/config'
import { toastMsg } from '@/toast'
import Talent from '@/components/Talent'
import './Pick.css'
export function Pick() {
const [pulled, puller] = useTalentPuller()
const [talents, picker] = useTalentPicker()
const { enabled, min, max } = useSubmitIsEnable()
const submit = useTalentSubmit()
const msg = enabled
? '下一步'
: `请选取 ${min == max ? min : `${min}~${max}`} 个天赋`
const handleSubmit = () => {
if (enabled) return submit()
toastMsg(msg, 'pick-toast')
}
if (!pulled)
return (
<div className="screen talent-pick">
<button
className="primary font-mono focus"
onClick={() => puller()}
>
{PullCount} !
</button>
</div>
)
const ps = dev.locked
? Array.from(new Set([...dev.locked, ...pulled]))
: pulled
return (
<div className="screen talent-pick">
<ul className="talent-list">
{ps.map(id => (
<li key={id} onClick={() => picker(id)}>
<Talent id={id} selected={talents.has(id)} />
</li>
))}
</ul>
<button
className={enabled ? 'primary' : 'error'}
onClick={handleSubmit}
>
{msg}
</button>
</div>
)
}
export default Pick
+165
View File
@@ -0,0 +1,165 @@
.screen.play {
height: calc(100dvh - 2rem);
gap: 1rem;
> ul.properties {
display: flex;
flex-direction: row;
gap: 0.5rem;
width: 100%;
font-weight: bold;
> li {
display: flex;
flex-direction: column;
flex: 1 0;
justify-content: space-between;
text-align: center;
min-width: 6rem;
user-select: none;
color: var(--grade-color);
span {
display: flex;
justify-content: center;
align-items: center;
border: 0.0625rem solid var(--grade-color);
box-sizing: border-box;
height: 2rem;
&:first-child {
border-bottom: none;
}
}
.name {
color: var(--base-background-color);
background-color: var(--grade-color);
}
&.charm { order: 1 }
&.intelligence { order: 2 }
&.strength { order: 3 }
&.money { order: 4 }
&.spirit { order: 5 }
}
}
> ul.logs {
display: flex;
flex-direction: column;
flex: 1 0;
gap: 0.5rem;
padding: 0 0.5rem;
min-width: 32rem;
max-width: 100%;
height: 100%;
border: 0.0625rem solid var(--base-text-color);
box-sizing: border-box;
> li.log {
display: flex;
flex-direction: row;
line-height: 2rem;
gap: 0.5rem;
width: 100%;
user-select: none;
cursor: pointer;
> .age {
flex-shrink: 0;
width: 3rem;
height: 2rem;
text-align: right;
color: var(--color-primary);
font-weight: bold;
/* border-bottom: 0.0625rem solid var(--base-text-color); */
box-sizing: border-box;
}
> .content {
position: relative;
&::before {
content: '';
position: absolute;
left: -0.5rem;
bottom: 0;
height: calc(100% - 2rem);
width: calc(100% + 0.5rem);
border: 0.0625rem solid var(--base-text-color);
border-top: none;
border-right: none;
box-sizing: border-box;
pointer-events: none;
}
.tag, .name {
font-weight: bold;
}
}
}
>li:last-child {
margin-bottom: 2rem;
}
}
ul.log-inner {
> li {
color: var(--grade-color);
display: flex;
flex-direction: row;
gap: 0.5rem;
}
}
.controls {
display: flex;
flex-direction: row;
gap: 0.5rem;
width: 100%;
> button {
flex: 1 0;
min-width: 6rem;
font-size: calc(1.25rem * var(--ratio-font));
height: calc(2.5rem * var(--ratio));
}
}
}
.screen.play > ul.properties > li {
.value {
position: relative;
background-color: var(--base-background-color);
transition: background-color 0.5s ease;
}
&[class*="trend-up"] .value::after,
&[class*="trend-down"] .value::after {
content: '';
position: absolute;
right: 0.5rem;
top: 50%;
transform: translateY(-50%);
border-style: solid;
animation: triangle-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
&[class*="trend-up"] .value::after {
border-width: 0 0.3125rem 0.375rem 0.3125rem;
border-color: transparent transparent var(--color-success) transparent;
}
&[class*="trend-down"] .value::after {
border-width: 0.375rem 0.3125rem 0 0.3125rem;
border-color: var(--color-error) transparent transparent transparent;
}
&[class*="trend-up-1"] .value { animation: flash-success-odd 0.5s ease-out forwards; }
&[class*="trend-up-0"] .value { animation: flash-success-even 0.5s ease-out forwards; }
&[class*="trend-down-1"] .value { animation: flash-error-odd 0.5s ease-out forwards; }
&[class*="trend-down-0"] .value { animation: flash-error-even 0.5s ease-out forwards; }
}
@keyframes flash-success-odd {
0% { background-color: color-mix(in srgb, var(--color-success) 30%, var(--base-background-color)); }
100% { background-color: var(--base-background-color); }
}
@keyframes flash-success-even {
0% { background-color: color-mix(in srgb, var(--color-success) 30%, var(--base-background-color)); }
100% { background-color: var(--base-background-color); }
}
@keyframes flash-error-odd {
0% { background-color: color-mix(in srgb, var(--color-error) 30%, var(--base-background-color)); }
100% { background-color: var(--base-background-color); }
}
@keyframes flash-error-even {
0% { background-color: color-mix(in srgb, var(--color-error) 30%, var(--base-background-color)); }
100% { background-color: var(--base-background-color); }
}
@keyframes triangle-pop {
0% { opacity: 0; transform: translateY(-50%) scale(0.5); }
100% { opacity: 1; transform: translateY(-50%) scale(1); }
}
+203
View File
@@ -0,0 +1,203 @@
import { useState, useRef, useCallback } from 'react'
import { useLayoutEffect, useEffect } from 'react'
import { useNext, useGotoSummary, type Log } from '@remake/hooks'
import { useJudge } from '@/hooks/judge'
import { achievements, events, talents } from '@remake/data'
import { properties } from '@/display'
import { AutoInterval } from '@/config'
import { format } from '@remake/vitex'
import { toastAchvs } from '@/toast/Achv'
import './Play.css'
function LogTalent({ id }: { id: number }) {
const { name, description, grade } = talents.get(id)!
return (
<li className={`grade-${grade}`}>
<span className="tag font-mono">[]</span>
<span className="name">{name}</span>
<span className="description">{description}</span>
</li>
)
}
function LogTalents({ items }: { items: number[] }) {
if (items.length === 0) return null
const els = items.map(id => <LogTalent key={id} id={id} />)
return <ul className="log-inner log-talents">{els}</ul>
}
const year = new Date().getFullYear()
interface LogEventProps {
id: number
post: boolean
index: number
}
function LogEvent({ id, post, index }: LogEventProps) {
let { event, postEvent, grade, format: f } = events.get(id)!
if (f) {
const g = (key: string) => ({ CurrentYear: year + index })[key]
event = format(event, g)
if (post && postEvent) postEvent = format(postEvent, g)
}
return (
<>
<li className={`grade-${grade}`}>{event}</li>
{post && postEvent && (
<li className={`grade-${grade}`}>{postEvent}</li>
)}
</>
)
}
function LogEvents({ items, index }: { items: number[]; index: number }) {
const last = items.length - 1
const els = items.map((id, i) => (
<LogEvent key={id} id={id} post={i == last} index={index} />
))
return <ul className="log-inner log-events">{els}</ul>
}
function LogAchievement({ id }: { id: number }) {
const { name, description, grade } = achievements.get(id)!
return (
<li className={`grade-${grade}`}>
<span className="tag font-mono">[]</span>
<span className="name">{name}</span>
<span className="description">{description}</span>
</li>
)
}
function LogAchievements({ items }: { items: number[] }) {
if (items.length === 0) return null
const els = items.map(id => <LogAchievement key={id} id={id} />)
return <ul className="log-inner log-achievements">{els}</ul>
}
function Log({ log, index }: { log: Log; index: number }) {
return (
<li className="log">
<span className="age font-mono">{log.age}</span>
<div className="content">
<LogTalents items={log.talents} />
<LogEvents items={log.events} index={index} />
<LogAchievements items={log.achievements} />
</div>
</li>
)
}
interface PropProps {
prop: keyof typeof properties
value: number
grade: number
}
function Prop({ prop, value, grade }: PropProps) {
const prevRef = useRef<number>(value)
const [trend, setTrend] = useState<'up' | 'down' | 'normal'>('normal')
const [flip, setFlip] = useState(0)
const [displayValue, setDisplayValue] = useState<number>(value)
useEffect(() => {
const prev = prevRef.current
if (value === prev) return
const startValue = prevRef.current
prevRef.current = value
setTrend(value > prev ? 'up' : 'down')
setFlip(f => (f + 1) % 2)
let startTimestamp: number | null = null
const duration = 400
let end = false
const step = (timestamp: number) => {
if (!startTimestamp) startTimestamp = timestamp
const progress = timestamp - startTimestamp
const progressRatio = Math.min(progress / duration, 1)
const easeOutQuad = progressRatio * (2 - progressRatio)
const currentDec = startValue + (value - startValue) * easeOutQuad
setDisplayValue(Math.round(currentDec))
if (!end && progress < duration) requestAnimationFrame(step)
}
requestAnimationFrame(step)
const timer = setTimeout(() => setTrend('normal'), 2000)
return () => {
clearTimeout(timer)
setDisplayValue(value)
end = true
}
}, [value])
return (
<li className={`${prop} grade-${grade} trend-${trend}-${flip}`}>
<span className="name">{properties[prop]}</span>
<span className="value font-mono">{displayValue}</span>
</li>
)
}
function Properties() {
const judges = useJudge()
return (
<ul className="properties">
{judges.map(([key, { value, grade }]) => (
<Prop key={key} prop={key} value={value} grade={grade} />
))}
</ul>
)
}
export function Play() {
const [{ logs, ended }, next] = useNext()
const [auto, setAuto] = useState(false)
const logRef = useRef<HTMLUListElement>(null)
const autoRef = useRef(0)
const gotoSummary = useGotoSummary()
const handleNext = useCallback(() => {
if (ended) return
const achievements = next()
toastAchvs(achievements)
}, [ended, next])
const handleGotoSummary = useCallback(() => {
if (!ended) return
const achievements = gotoSummary()
toastAchvs(achievements)
}, [ended, gotoSummary])
useLayoutEffect(() => {
requestAnimationFrame(() => {
if (!logRef.current) return
logRef.current.scrollTop = logRef.current.scrollHeight
})
}, [logs])
useEffect(() => {
if (!auto) window.clearInterval(autoRef.current)
else autoRef.current = window.setInterval(handleNext, AutoInterval)
return () => window.clearInterval(autoRef.current)
}, [auto, handleNext])
return (
<div className="screen play">
<Properties />
<ul
className="logs hide-scrollbar"
onClick={handleNext}
ref={logRef}
>
{logs.map((log, index) => (
<Log key={index} log={log} index={index} />
))}
</ul>
<div className="controls">
{!ended && (
<button className="primary" onClick={() => setAuto(!auto)}>
{auto ? '关闭自动' : '开启自动'}
</button>
)}
{ended && (
<button className="primary" onClick={handleGotoSummary}>
</button>
)}
</div>
</div>
)
}
export default Play
+43
View File
@@ -0,0 +1,43 @@
.screen.summary {
gap: 1rem;
ul.judge-list {
display: flex;
flex-direction: row;
gap: 0.125rem;
width: 100%;
> li {
display: flex;
flex-direction: column;
flex: 1 0;
justify-content: space-between;
text-align: center;
min-width: 4rem;
user-select: none;
span {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
font-weight: bold;
border: 0.0625rem solid var(--grade-color);
box-sizing: border-box;
height: 2rem;
color: var(--grade-color);
&:not(:first-child) {
border-top: none;
}
}
.level {
font-size: 0.95rem;
background-color: var(--grade-color);
color: var(--base-background-color);
}
}
}
ul.talent-list {
display: flex;
flex-direction: column;
gap: calc(0.5rem * var(--ratio));
width: 100%;
}
}
+82
View File
@@ -0,0 +1,82 @@
import { useEffect, useRef } from 'react'
import { usePicked, useEnd, useProfile, useIsClassic } from '@remake/hooks'
import { useEndJudge } from '@/hooks/judge'
import { properties, judgeDisplay } from '@/display'
import { toastAchvs, toastMsg } from '@/toast'
import Talent from '@/components/Talent'
import './Summary.css'
function Judges() {
const judges = useEndJudge()
return (
<ul className="judge-list">
{judges.map(([key, { value, grade, level }]) => (
<li className={`${key} grade-${grade}`} key={key}>
<span className="property">{properties[key]}</span>
<span className="value font-mono">{value}</span>
<span className="level">{judgeDisplay(key, level)}</span>
</li>
))}
</ul>
)
}
interface TalentListProps {
picked: Set<number>
picker: (id: number) => void
}
function TalentList({ picked, picker }: TalentListProps) {
const [profile] = useProfile()
const talents = usePicked()
const hasInitialized = useRef(false)
useEffect(() => {
if (!profile.locked) return
if (!hasInitialized.current) {
for (const id of profile.locked) {
if (talents.has(id) && !picked.has(id)) {
picker(id)
}
}
hasInitialized.current = true
}
}, [profile.locked, talents, picked, picker])
return (
<ul className="talent-list">
{Array.from(talents, id => (
<li key={id} onClick={() => picker(id)}>
<Talent id={id} selected={picked.has(id)} />
</li>
))}
</ul>
)
}
export default function Summary() {
const isClassic = useIsClassic()
const [locked, picker, end] = useEnd()
const handlePicker = (id: number) => {
if (isClassic) picker(id)
else toastMsg('名人天赋不可锁定', 'summary-toast')
}
const handleEnd = () => {
const achievements = end()
toastAchvs(achievements)
}
return (
<div className="screen summary">
<Judges />
<div className="section">
<div className="title">
{isClassic
? '你可以锁定一个天赋,下辈子还能抽到'
: '名人天赋不可锁定'}
</div>
<TalentList picked={locked} picker={handlePicker} />
</div>
<button className="primary" onClick={handleEnd}>
</button>
</div>
)
}
+95
View File
@@ -0,0 +1,95 @@
.screen.thanks {
height: calc(100dvh - 2rem);
gap: 1rem;
.person {
--style-color: var(--custom-color, var(--base-text-color));
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
color: var(--style-color);
background: var(--base-background-color);
transition: background 0.2s ease-in-out, color 0.2s ease-in-out;
> .name {
display: flex;
text-wrap: wrap;
word-break: break-all;
min-height: 2.5rem;
width: 12rem;
font-weight: bold;
font-size: 1.2rem;
align-items: center;
text-shadow:
-0.0625rem -0.0625rem 0.0625rem var(--base-shadow),
0.0625rem -0.0625rem 0.0625rem var(--base-shadow),
-0.0625rem 0.0625rem 0.0625rem var(--base-shadow),
0.0625rem 0.0625rem 0.0625rem var(--base-shadow),
0 -0.0625rem 0.0625rem var(--base-shadow),
0 0.0625rem 0.0625rem var(--base-shadow),
-0.0625rem 0 0.0625rem var(--base-shadow),
0.0625rem 0 0.0625rem var(--base-shadow);
}
> .comment {
word-break: break-all;
white-space: pre-wrap;
min-height: 2.5rem;
flex: 1 0;
display: flex;
align-items: center;
text-shadow:
-0.0625rem -0.0625rem 0.0625rem var(--base-shadow),
0.0625rem -0.0625rem 0.0625rem var(--base-shadow),
-0.0625rem 0.0625rem 0.0625rem var(--base-shadow),
0.0625rem 0.0625rem 0.0625rem var(--base-shadow),
0 -0.0625rem 0.0625rem var(--base-shadow),
0 0.0625rem 0.0625rem var(--base-shadow),
-0.0625rem 0 0.0625rem var(--base-shadow),
0.0625rem 0 0.0625rem var(--base-shadow);
}
}
> .group-1 {
flex: 1 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
border: 0.0625rem solid var(--base-text-color);
box-sizing: border-box;
padding: 0.5rem 0;
> .person {
margin: 0 0.5rem;
padding: 0 0.5rem;
display: flex;
border: 0.0625rem solid var(--style-color);
box-sizing: border-box;
> .comment {
word-break: break-all;
white-space: pre-wrap;
min-height: 2.5rem;
flex: 1 0;
padding: 0 0.5rem;
display: flex;
align-items: center;
}
}
}
> .group-2 {
flex: 3 1;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
gap: 0.5rem;
border: 0.0625rem solid var(--base-text-color);
> .person {
display: flex;
justify-content: center;
align-items: center;
text-wrap: wrap;
word-break: break-all;
text-align: center;
margin-right: 0.0625rem;
> .name { justify-content: center; }
> .comment {display: none;}
}
}
}
+47
View File
@@ -0,0 +1,47 @@
import { useGoHome } from '@remake/hooks'
import { specialThanks, SpecialThanksGroup as Group } from '@remake/data'
import type { SpecialThanks } from '@remake/data'
import { shuffle } from '@remake/vitex'
import './Thanks.css'
const groups = Map.groupBy(specialThanks, item => item.group)
export function Person({ item }: { item: SpecialThanks }) {
return (
<li
className="person"
style={
{
'--custom-color': item.color,
} as React.CSSProperties
}
>
<span className="name">{item.name}</span>
{item.comment && <span className="comment">{item.comment}</span>}
</li>
)
}
export function Thanks() {
const goHome = useGoHome()
const group1 = shuffle(groups.get(Group.G1) ?? [])
const group2 = shuffle(groups.get(Group.G2) ?? [])
return (
<div className="screen thanks">
<button className="primary" onClick={goHome}>
</button>
<ul className="group group-1 hide-scrollbar">
{group1.map((item, index) => (
<Person key={index} item={item} />
))}
</ul>
<ul className="group group-2 hide-scrollbar">
{group2.map((item, index) => (
<Person key={index} item={item} />
))}
</ul>
</div>
)
}
export default Thanks
+31
View File
@@ -0,0 +1,31 @@
export const properties = {
charm: '颜值',
intelligence: '智力',
strength: '体质',
money: '家境',
spirit: '快乐',
age: '享年',
summary: '总评',
}
const s = (str: string) => str.split('|')
const p = (prefix: string, arr: string[]) => arr.map(v => prefix + v)
const jbase = '地狱|折磨|不佳|普通|优秀|罕见|逆天|传说'
export const judges = {
summary: s(jbase),
charm: s(jbase),
money: s(jbase),
spirit: s('地狱|折磨|不幸|普通|幸福|极乐|天命'),
intelligence: s(jbase + '|识海|元神|仙魂'),
strength: s(jbase + '|凝气|筑基|金丹|元婴|仙体'),
age: s('胎死腹中|早夭|少年|盛年|中年|花甲|古稀|杖朝|南山|不老|修仙|仙寿'),
}
export const rates = {
times: p('抽到紫色概率', s('不变|翻倍|三倍|四倍|五倍|六倍')),
achievement: p('抽到橙色概率', s('不变|翻倍|三倍|四倍|五倍|六倍')),
}
export function judgeDisplay(key: keyof typeof judges, level: number) {
return judges[key][level]
}
+46
View File
@@ -0,0 +1,46 @@
import { useMemo } from 'react'
import { useSummary, useGameState } from '@remake/hooks'
import { judge, judgeGrade } from '@/config'
import type { Judges } from '@/config'
import { keys } from '@remake/vitex'
export interface Judge {
value: number
level: number
grade: number
}
function judges<T extends Judges, K extends keyof Record<T, number> = never>(
props: Record<T, number>,
filter?: K[],
) {
const judges: [Exclude<T, K>, Judge][] = []
for (const key of keys(props, filter)) {
const value = props[key]
const level = judge(key, value)
const grade = judgeGrade(key, level)
judges.push([key, { value, level, grade }])
}
return judges
}
export const useJudge = () => {
const state = useGameState()
if (!state) throw new Error('Game state is not set')
const current = state.props.current
return useMemo(() => {
const result = judges(current, ['age'])
return result
}, [current])
}
export const useEndJudge = () => {
const summary = useSummary()
const state = useGameState()
if (!state) throw new Error('Game state is not set')
const highest = state.props.highest
return useMemo(() => {
const props = { ...highest, summary }
const result = judges(props)
return result
}, [highest, summary])
}
+63
View File
@@ -0,0 +1,63 @@
import { useEffect, useCallback, useTransition } from 'react'
import { atom, useAtom } from 'jotai'
import { useConfigInject, useProfileInject, useRawProfile } from '@remake/hooks'
import { useUniqueInject, useUnique } from '@remake/hooks'
import { init, get, set } from '@/storage'
import { config } from '@/config'
const initedAtom = atom(false)
export const useInit = () => {
const configInject = useConfigInject()
const profileInject = useProfileInject()
const uniqueInject = useUniqueInject()
const [inited, setInited] = useAtom(initedAtom)
const loader = useCallback(async () => {
if (inited) return
configInject(config)
await init()
const profile = await get('profile')
const parsed = profile ? JSON.parse(profile) || {} : {}
profileInject({
...parsed,
times: parsed.times || 0,
achievements: new Set(parsed.achievements || []),
events: new Set(parsed.events || []),
talents: new Set(parsed.talents || []),
})
const unique = await get('unique')
if (unique) uniqueInject(JSON.parse(unique))
setInited(true)
}, [inited, configInject, profileInject, uniqueInject, setInited])
return [inited, loader] as const
}
export const useWatcher = () => {
const [profile] = useRawProfile()
const unique = useUnique()
const [inited] = useAtom(initedAtom)
const [p, saveProfile] = useTransition()
const [u, saveUnique] = useTransition()
useEffect(() => {
if (!inited || !profile) return
const str = JSON.stringify({
...profile,
times: profile.times,
achievements: Array.from(profile.achievements),
events: Array.from(profile.events),
talents: Array.from(profile.talents),
})
saveProfile(async () => {
await set('profile', str)
})
}, [inited, profile, saveProfile])
useEffect(() => {
if (!inited || !unique) return
const str = JSON.stringify(unique)
saveUnique(async () => {
await set('unique', str)
})
}, [inited, unique, saveUnique])
return p || u
}
+13
View File
@@ -0,0 +1,13 @@
import { createRoot } from 'react-dom/client'
import { StrictMode } from 'react'
import { Game } from './Game'
import './styles/colors.css'
import './styles/common.css'
const container = document.getElementById('remake')!
const root = createRoot(container)
root.render(
<StrictMode>
<Game />
</StrictMode>,
)
+15
View File
@@ -0,0 +1,15 @@
import { test, expect, describe } from 'vitest'
import { talents, achievements, events } from '@remake/data'
describe('Bili Toy Cloud Save Core', () => {
test('maxSizeProfile should pass ToyCloudSaveCore constraints', () => {
const json = JSON.stringify({
times: 999999,
locked: Array.from(talents.keys()),
talents: Array.from(talents.keys()),
achievements: Array.from(achievements.keys()),
events: Array.from(events.keys()),
})
expect(json.length).toBeLessThan((48 * 960 * 3) / 4)
})
})
+23
View File
@@ -0,0 +1,23 @@
import * as biliToy from '@remake/thirdparty-bili-toy'
import * as local from './local'
interface Storage {
init(): Promise<void>
get(key: string): Promise<string | null>
set(key: string, value: string): Promise<boolean>
}
const storage = {} as Storage
if (import.meta.env.VITE_CHANNEL === 'bili') {
storage.init = biliToy.init
storage.get = biliToy.get
storage.set = biliToy.set
} else {
storage.init = async () => {}
storage.get = local.get
storage.set = local.set
}
export const init = storage.init
export const get = storage.get
export const set = storage.set
+39
View File
@@ -0,0 +1,39 @@
if (localStorage.getItem('version') !== '3.0.0') {
const times = parseInt(localStorage.getItem('times') || '0')
if (times) {
const achievements = (
JSON.parse(localStorage.getItem('ACHV') || '[]') as [
number,
number,
][]
)
.sort((a, b) => a[1] - b[1])
.map(([id]) => id)
const events = JSON.parse(localStorage.getItem('AEVT') || '[]')
const talents = JSON.parse(localStorage.getItem('ATLT') || '[]')
const profile = { times, achievements, events, talents } as any
const lockedTalents = JSON.parse(
localStorage.getItem('extendTalent') || 'null',
)
if (lockedTalents) profile.locked = [lockedTalents]
localStorage.setItem('profile', JSON.stringify(profile))
localStorage.removeItem('times')
localStorage.removeItem('ACHV')
localStorage.removeItem('AEVT')
localStorage.removeItem('ATLT')
localStorage.removeItem('extendTalent')
const unique = localStorage.getItem('uniqueWaTaShi')
if (unique) localStorage.setItem('unique', unique)
localStorage.removeItem('uniqueWaTaShi')
}
localStorage.setItem('version', '3.0.0')
}
export async function get(key: string) {
return localStorage.getItem(key)
}
export async function set(key: string, value: string) {
localStorage.setItem(key, value)
return true
}
+22
View File
@@ -0,0 +1,22 @@
:root {
color-scheme: light dark;
--base-text-color: light-dark(#121314, #f9fafb);
--base-background-color: light-dark(#f7f7f7, #121314);
--base-shadow: light-dark(#00000010, #23456710);
--color-grade-0: light-dark(#4e5052, #f9fafb);
--color-grade-1: light-dark(#4aa8da, #afe3ff);
--color-grade-2: light-dark(#b03fe4, #e09eff);
--color-grade-3: light-dark(#ffaf2d, #ffe48d);
--color-success: light-dark(#61b700, #b9ff69);
--color-info: light-dark(#454ad3, #b5b7ff);
--color-error: light-dark(#e42b2b, #ff6969);
--color-primary: var(--color-success);
--color-secondary: var(--color-info);
}
.grade-0 { --grade-color: var(--color-grade-0); }
.grade-1 { --grade-color: var(--color-grade-1); }
.grade-2 { --grade-color: var(--color-grade-2); }
.grade-3 { --grade-color: var(--color-grade-3); }
+134
View File
@@ -0,0 +1,134 @@
:root {
--ratio: 1;
--ratio-font: 1;
color: var(--base-text-color);
}
html { transition: all 0.2s ease-in-out; }
html[data-theme='light'] { color-scheme: light; }
html[data-theme='dark'] { color-scheme: dark; }
.hide-scrollbar,
html {
overflow-y: auto;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar { display: none; }
}
body {
min-height: calc(100dvh - 2rem);
width: calc(100dvw - 2rem);
display: grid;
place-items: center;
margin: 1rem;
padding: 0;
background: var(--base-background-color);
transition: all 0.2s ease-in-out;
}
html, body, button, input, select, textarea {
font-family:
-apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "PingFang SC",
"Noto Sans SC", "Noto Sans CJK SC", "Noto Sans",
"Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.font-mono {
font-family:
"SF Mono", SFMono-Regular, Menlo, Monaco, Consolas,
"Noto Sans Mono CJK SC", "Noto Sans Mono", "Noto Code",
ui-monospace, monospace;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
button {
--style-color: var(--base-text-color);
--style-background: var(--base-background-color);
--border-color: var(--style-color);
--background-color: var(--style-background);
--text-color: var(--style-color);
color: var(--text-color);
cursor: pointer;
box-sizing: border-box;
border: var(--border-color) 0.0625rem solid;
background: var(--background-color);
font-size: calc(1.2rem * var(--ratio-font));
font-weight: bold;
padding: 0 1rem;
height: calc(2.5rem * var(--ratio));
display: inline-flex;
align-items: center;
justify-content: center;
user-select: none;
transition: all 0.2s ease-in-out;
outline: none;
-webkit-tap-highlight-color: transparent;
@media (hover: hover) {
&:hover {
background: var(--style-color);
color: var(--style-background);
}
}
&:active {
background: var(--style-color);
color: var(--style-background);
transition: none;
}
&.primary { --style-color: var(--color-primary); }
&.secondary { --style-color: var(--color-secondary); }
&.info { --style-color: var(--color-info); }
&.error { --style-color: var(--color-error); }
&.focus {
display: flex;
align-items: center;
justify-content: center;
height: calc(3.5rem * var(--ratio));
font-size: calc(1.6rem * var(--ratio-font));
font-weight: bold;
}
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
input {
font-size: calc(1rem * var(--ratio-font));
padding: 0;
margin: 0;
border: none;
background: var(--base-background-color);
text-align: center;
-moz-appearance: textfield;
appearance: none;
color: var(--base-text-color);
transition: all 0.2s ease-in-out;
&:focus { outline: none; }
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
}
input[type="number"] {
-moz-appearance: textfield;
appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0; /* 移除某些浏览器下的默认留白 */
}
}
@media (max-width: 32rem) { :root { font-size: 2.88vw; } }
@media screen and (orientation: portrait) and (pointer: coarse) {
:root {
--ratio: 1.5;
--ratio-font: 1.2;
}
}
+40
View File
@@ -0,0 +1,40 @@
.achv-toast-container {
position: fixed;
width: min(calc(100dvw - 2rem), 48rem);
top: 6rem;
left: 1rem;
pointer-events: none;
user-select: none;
.toast-item {
display: flex;
position: absolute;
opacity: 1;
transition: all 0.2s ease-in-out;
&.visible {
animation: achv-slide-in 0.2s ease-out forwards;
}
&.hidden {
animation: achv-fade-out 0.2s ease-out forwards;
}
}
}
@keyframes achv-slide-in {
0% { opacity: 0; transform: translateX(-100%); }
100% { opacity: 1; transform: translateX(0); }
}
@keyframes achv-slide-out {
0% { opacity: 1; transform: translateX(0); }
100% { opacity: 0; transform: translateX(-100%); }
}
@keyframes achv-fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes achv-fade-out {
0% { opacity: 1; }
100% { opacity: 0; }
}
+49
View File
@@ -0,0 +1,49 @@
import { useToaster, toast } from 'react-hot-toast/headless'
import Achievement from '@/components/Achievement'
import './Achv.css'
export function ToastAchvContainer() {
const { toasts, handlers } = useToaster({ duration: 4000 }, 'achv-toast')
const { startPause, endPause, calculateOffset, updateHeight } = handlers
return (
<div
className="achv-toast-container"
onMouseEnter={startPause}
onMouseLeave={endPause}
>
{toasts.map(toast => {
const offset = calculateOffset(toast, {
reverseOrder: true,
gutter: 8,
})
return (
<div
className={`toast-item ${toast.visible ? 'visible' : 'hidden'}`}
key={toast.id}
ref={el => {
if (el && typeof toast.height !== 'number') {
const height = el.getBoundingClientRect().height
updateHeight(toast.id, height)
}
}}
style={{
top: `${offset}px`,
}}
>
{typeof toast.message == 'function'
? toast.message(toast)
: toast.message}
</div>
)
})}
</div>
)
}
export default ToastAchvContainer
export function toastAchvs(achievements: number[]) {
if (achievements.length === 0) return
for (const id of achievements) {
toast(<Achievement id={id} />, { id: '' + id, toasterId: 'achv-toast' })
}
}
+45
View File
@@ -0,0 +1,45 @@
.msg-toast-container {
position: fixed;
width: min(calc(100dvw - 2rem), 48rem);
top: 6rem;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
pointer-events: none;
user-select: none;
.toast-item {
display: flex;
position: absolute;
opacity: 1;
transition: all 0.2s ease-in-out;
&.visible {
animation: msg-slide-in 0.2s ease-out forwards;
}
&.hidden {
animation: msg-fade-out 0.2s ease-out forwards;
}
}
}
@keyframes msg-slide-in {
0% { opacity: 0; transform: translateY(-100%); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes msg-slide-out {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-100%); }
}
@keyframes msg-fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes msg-fade-out {
0% { opacity: 1; }
100% { opacity: 0; }
}
+46
View File
@@ -0,0 +1,46 @@
import { useToaster, toast } from 'react-hot-toast/headless'
import Message from '@/components/Message'
import './Msg.css'
export function ToastMsgContainer() {
const { toasts, handlers } = useToaster({ duration: 1500 }, 'msg-toast')
const { startPause, endPause, calculateOffset, updateHeight } = handlers
return (
<div
className="msg-toast-container"
onMouseEnter={startPause}
onMouseLeave={endPause}
>
{toasts.map(toast => {
const offset = calculateOffset(toast, {
reverseOrder: true,
gutter: 8,
})
return (
<div
className={`toast-item ${toast.visible ? 'visible' : 'hidden'}`}
key={toast.id}
ref={el => {
if (el && typeof toast.height !== 'number') {
const height = el.getBoundingClientRect().height
updateHeight(toast.id, height)
}
}}
style={{
top: `${offset}px`,
}}
>
{typeof toast.message == 'function'
? toast.message(toast)
: toast.message}
</div>
)
})}
</div>
)
}
export default ToastMsgContainer
export function toastMsg(message: string, key: string) {
toast(<Message message={message} />, { id: key, toasterId: 'msg-toast' })
}
+13
View File
@@ -0,0 +1,13 @@
export * from './Msg'
export * from './Achv'
import ToastAchvContainer from './Achv'
import ToastMsgContainer from './Msg'
export const ToastContainer = () => (
<>
<ToastAchvContainer />
<ToastMsgContainer />
</>
)
export default ToastContainer
+13
View File
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"jsx": "react-jsx",
"types": ["react", "vite/client"],
"jsxImportSource": "react",
"paths": {
"@/*": ["./src/*"],
},
},
"include": ["**/*"]
}
+33
View File
@@ -0,0 +1,33 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
const dataSplitRule = {
test: /[\\/]packages[\\/]data[\\/]|@remake\/data/,
priority: 40,
name(id: string) {
const name = id.split(/[\/\\]/).pop()!
const baseName = name.substring(0, name.lastIndexOf('.'))
if (baseName && baseName !== 'index') return `data-${baseName}`
return null
},
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: { tsconfigPaths: true },
define: { 'import.meta.env.VITE_CHANNEL': '"bili"' },
base: './',
build: {
outDir: 'dist/bili/remake',
chunkSizeWarningLimit: 1500,
rolldownOptions: {
output: {
codeSplitting: {
minSize: 1024,
groups: [dataSplitRule],
},
},
},
},
})
+31
View File
@@ -0,0 +1,31 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
const dataSplitRule = {
test: /[\\/]packages[\\/]data[\\/]|@remake\/data/,
priority: 40,
name(id: string) {
const name = id.split(/[\/\\]/).pop()!
const baseName = name.substring(0, name.lastIndexOf('.'))
if (baseName && baseName !== 'index') return `data-${baseName}`
return null
},
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: { tsconfigPaths: true },
base: './',
build: {
chunkSizeWarningLimit: 1500,
rolldownOptions: {
output: {
codeSplitting: {
minSize: 1024,
groups: [dataSplitRule],
},
},
},
},
})
File diff suppressed because it is too large Load Diff
Binary file not shown.
-504
View File
File diff suppressed because one or more lines are too long
-57046
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
-1572
View File
File diff suppressed because it is too large Load Diff
-12263
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
-133
View File
@@ -1,133 +0,0 @@
$id,name,description,condition,grade,status,effect:SPR,effect:MNY,effect:CHR,effect:STR,effect:INT,exclusive[],exclusive[],exclusive[],exclusive[],exclusive[],exclusive[]
序号,天赋名,括号中的内容,触发条件,稀有度,初始可用属性点,额外快乐,额外家境,额外颜值,额外体质,额外智力,互斥天赋,互斥天赋,互斥天赋,互斥天赋,互斥天赋,互斥天赋
1001,随身玉佩,或许有护佑作用,,0,,,,,,,,,,,,
1002,红肚兜,小时候死亡率降低,,0,,,,,,,,,,,,
1003,生而为男,性别一定为男,,1,,,,,,,1004,1025,1024,1113,,
1004,生而为女,性别一定为女,,1,,,,,,,1003,1024,1025,,,
1005,动漫高手,入宅的可能性翻6倍,,2,,,,,,,,,,,,
1006,乐天派,快乐+1,,0,,1,,,,,,,,,,
1007,天赋异禀,初始可用属性点+2,,1,2,,,,,,,,,,,
1008,天生抑郁,快乐-3,,0,,-3,,,,,,,,,,
1009,网络巨魔,快乐+2,,1,,2,,,,,,,,,,
1010,天龙人,你拥有北京户口,,2,,,,,,,1012,1013,1014,,,
1011,独生子女,你没有兄弟姐妹,,0,,,,,,,,,,,,
1012,乡间微风,你出生在农村,,0,,,,,,,1010,1013,1014,,,
1013,城中高楼,你出生在城市,,0,,,,,,,1010,1012,1014,,,
1014,美籍华人,你有美国国籍,,2,,,3,,,,1010,1012,1013,,,
1015,家中老大,你最受父母宠爱,,1,,,,,,,,,,,,
1016,水性良好,不会被淹死,,0,,,,,,,,,,,,
1017,先天免疫,你不会得艾滋病,,0,,,,,,,,,,,,
1018,人类进化,所有属性+1,,2,,1,1,1,1,1,,,,,,
1019,超凡,初始可用属性点+4,,2,4,,,,,,,,,,,
1020,父母美貌,颜值+2,,1,,,,2,,,,,,,,
1021,红颜薄命,颜值+2,体质-2,,0,,,,2,-2,,,,,,,
1022,属蛇,不会被蛇咬死,,0,,,,,,,,,,,,
1023,半神,所有属性+2,,3,,2,2,2,2,2,,,,,,
1024,人中龙凤,天生双重性别,,2,,,,,,,1003,1004,1025,,,
1025,阴阳之外,天生无性别,,2,,,,,,,1003,1024,1004,1113,,
1026,彩虹之下,可能和同性交往,,0,,,,,,,1113,,,,,
1027,斩情证道,终生不恋爱结婚,,1,,,,,,,1113,,,,,
1028,桃花连连,恋爱机会提升,,0,,,,,,,,,,,,
1029,平安童年,12岁前父母都健在,,1,,,,,,,,,,,,
1030,宠物大师,宠物不会意外死亡,,0,,,,,,,,,,,,
1031,天生残疾,体质-2,,0,,,,,-2,,,,,,,
1032,早产儿,所有属性-1,,0,,-1,-1,-1,-1,-1,,,,,,
1033,十死无生,体质-10,,0,,,,,-10,,,,,,,
1034,家运不顺,家境-2,,0,,,-2,,,,,,,,,
1035,头着地,智力-2,,0,,,,,,-2,,,,,,
1036,胎教,智力+1,,0,,,,,,1,,,,,,
1037,班中红人,和同学容易处好关系,,0,,,,,,,,,,,,
1038,骑士,能轻松学会骑车,,0,,,,,,,,,,,,
1039,永远的神,电竞天才,,1,,,,,,,,,,,,
1040,戒律,赌毒不沾,,0,,,,,,,,,,,,
1041,丁克,不生孩子,,1,,,,,,,1113,,,,,
1042,少数民族,高考+5分,,0,,,,,,,,,,,,
1043,老司机,你和家人不会发生车祸,,0,,,,,,,,,,,,
1044,低压,你的家人不会心脏病,,0,,,,,,,,,,,,
1045,战功,你退伍后会当官,,0,,,,,,,,,,,,
1046,不孕不育,你生不出孩子,,1,,,,,,,1113,,,,,
1047,白头偕老,爱人至少能活到70岁,,1,,,,,,,,,,,,
1048,神秘的小盒子,100岁时才能开启,,3,,,,,,,,,,,,
1049,三十而立,30岁时家境+2,AGE?[30],0,,,2,,,,,,,,,
1050,四十不惑,40岁时智力+2,AGE?[40],0,,,,,,2,,,,,,
1051,知天命,50岁时智力、快乐+1,AGE?[50],0,,1,,,,1,,,,,,
1052,耳顺,60岁时快乐+2,AGE?[60],0,,2,,,,,,,,,,
1053,从心所欲,70岁时快乐+3,AGE?[70],0,,3,,,,,,,,,,
1054,老当益壮,60岁时体质+2,AGE?[60],1,,,,,2,,,,,,,
1055,鹤发童颜,70岁时颜值+3,AGE?[70],0,,,,3,,,,,,,,
1056,学前启蒙,5岁时智力+2,AGE?[5],1,,,,,,2,,,,,,
1057,十八变,18岁时颜值+2,AGE?[18],1,,,,2,,,,,,,,
1058,迟来之财,90岁时家境+4,AGE?[90],0,,,4,,,,,,,,,
1059,理财达人,30、40、50岁时家境+1,"AGE?[30,40,50]",0,,,1,,,,,,,,,
1060,成熟,12、18岁时智力+1,"AGE?[12,18]",1,,,,,,1,,,,,,
1061,形象管理,16、24岁时颜值+1,"AGE?[16,24]",1,,,,1,,,,,,,,
1062,成年礼,18岁时快乐+1,AGE?[18],0,,1,,,,,,,,,,
1063,开光之胎,初始可用属性点+1,,0,1,,,,,,,,,,,
1064,天命,初始可用属性点+8,,3,8,,,,,,,,,,,
1065,祖传药丸,功能不明,,1,,,,,,,,,,,,
1066,精准扶贫,家境为0时家境+1,(MNY<1)&(MNY>-1),0,,,1,,,,,,,,,
1067,乐天派,快乐为0时快乐+1,(SPR<1)&(SPR>-1),1,,1,,,,,,,,,,
1068,命悬一线,体质为0时体质+1,(STR<1)&(STR>-1),0,,,,,1,,,,,,,
1069,智可生财,若20岁时智力>8,家境+2,(AGE?[20])&(INT>8),0,,,2,,,,,,,,,
1070,舔狗甚多,若20岁时颜值>8,快乐+2,(AGE?[20])&(CHR>8),0,,2,,,,,,,,,,
1071,保胎丸,你不会胎死腹中,,0,,,,,,,,,,,,
1072,白化病,你不会遭遇枪击,,0,,,,,,,,,,,,
1073,佛宗,考上哈佛大学的几率提高,,0,,,,,,,,,,,,
1074,悟道,智力>10时快乐+3,INT>10,1,,3,,,,,,,,,,
1075,驻颜,体质>10时颜值+3,STR>10,0,,,,3,,,,,,,,
1076,界限突破,体质>10时快乐+3,STR>10,1,,3,,,,,,,,,,
1077,倾城,颜值>10时快乐+3,CHR>10,1,,3,,,,,,,,,,
1078,训练有方,智力>10时体质+3,INT>10,0,,,,,3,,,,,,,
1079,相由心生,智力>10时颜值+3,INT>10,0,,,,3,,,,,,,,
1080,智多鑫,智力>10时家境+3,INT>10,0,,,3,,,,,,,,,
1081,灵光,快乐>10时其他属性+1,SPR>10,0,,,1,1,1,1,,,,,,
1082,天启,快乐>10时其他属性+2,SPR>10,1,,,2,2,2,2,,,,,,
1083,神谕,快乐>10时其他属性+3,SPR>10,2,,,3,3,3,3,,,,,,
1084,献祭,初始可用属性点-2,快乐+2,,0,-2,2,,,,,1122,,,,,
1085,幸运儿,初始可用属性点-3,快乐+5,,1,-3,5,,,,,1122,,,,,
1086,挑战者,初始可用点-10,,0,-10,,,,,,1122,,,,,
1087,你不懂,家境>10时快乐+3,MNY>10,1,,3,,,,,,,,,,
1088,整容,家境>10时颜值+3,MNY>10,0,,,,3,,,,,,,,
1089,钻石健身卡,家境>10时体质+3,MNY>10,0,,,,,3,,,,,,,
1090,身残志坚,体质<0时其他属性+1,STR<0,0,,1,1,1,,1,,,,,,
1091,活死人,体质<-1时其他属性+2,STR<0,1,,2,2,2,,2,,,,,,
1092,开一扇窗,颜值<0时其他属性+1,CHR<0,0,,1,1,,1,1,,,,,,
1093,大额头,颜值-2,智力+2,,0,,,,-2,,2,,,,,,
1094,痘痘脸,颜值-1,,0,,,,-1,,,,,,,,
1095,潜能,家境<0时其他属性+1,MNY<0,0,,1,,1,1,1,,,,,,
1096,哀兵,快乐<0时其他属性+1,SPR<0,0,,,1,1,1,1,,,,,,
1097,苦痛侍僧,快乐<-1时其他属性+2,SPR<-1,1,,,2,2,2,2,,,,,,
1098,觉醒,家境<-1时其他属性+2,MNY<-1,1,,2,,2,2,2,,,,,,
1099,抖M,家境-2,快乐+2,,0,,2,-2,,,,,,,,,
1100,海的女儿,颜值-2,初始可用属性点+1,,0,1,,,-2,,,,,,,,
1101,进阶,所有属性>5时,所有属性+1,(SPR>5)&(MNY>5)&(CHR>5)&(STR>5)&(INT>5),0,,1,1,1,1,1,,,,,,
1102,超进化,所有属性>5时,所有属性+2,(SPR>5)&(MNY>5)&(CHR>5)&(STR>5)&(INT>5),1,,2,2,2,2,2,,,,,,
1103,白色胶囊,你10岁时无事发生,AGE?[10],0,,,,,,,,,,,,
1104,紫色胶囊,跳过你的40~50岁,"AGE?[40,41,42,43,44,45,46,47,48,49,50]",2,,,,,,,,,,,,
1105,蓝色胶囊,你20、30岁时无事发生,"AGE?[20,30]",1,,,,,,,,,,,,
1106,健康饮食,你不吃洋快餐,,0,,,,,,,,,,,,
1107,不想罢了,你不会上清华大学,,0,,,,,,,,,,,,
1108,挑衅,你喜欢没事找事,,0,,,,,,,,,,,,
1109,旅行者,你喜欢旅游,,0,,,,,,,,,,,,
1110,水仙,你比较自恋,,0,,,,,,,,,,,,
1111,缺一门,无效果,,0,,,,,,,,,,,,
1112,异界来客,你可能听到一些绝密消息,,2,,,,,,,,,,,,
1113,三胎人生,你尽可能生三胎,,1,,,,,,,1003,1025,1026,1027,1041,1046
1114,橙色胶囊,跳过你的60~90岁,"AGE?[60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90]",3,,,,,,,,,,,,
1115,宙斯,参加奥赛的几率提高,,0,,,,,,,,,,,,
1116,为人民服务,考公务员时一定能考上,,0,,,,,,,,,,,,
1117,表现良好,入狱会减刑,,0,,,,,,,,,,,,
1118,小吉,运气稍微提升,,0,,,,,,,,,,,,
1119,天秤座,据说做事很公平,,0,,,,,,,,,,,,
1120,万里挑一,你很攻,,0,,,,,,,,,,,,
1121,把握不住,你有强迫症,,0,,,,,,,,,,,,
1122,急了急了,赶着投胎,不要初始属性了,,1,-20,,,,,,1084,1085,1086,,,
1123,不离不弃,你不会离婚,,0,,,,,,,,,,,,
1124,足量,身高不矮,,0,,,,,,,,,,,,
1125,易胖体质,颜值更容易降低,,0,,,,,,,,,,,,
1126,黄帝,种族主义者,,0,,,,,,,,,,,,
1127,左撇子,习惯使用左手,,0,,,,,,,,,,,,
1128,克苏鲁,&▓▓▓◆▓▓▓¥#▓@■.◆,,2,,,,,,,,,,,,
1129,不连续存在,你还拥有其他人格,,2,,,,,,,,,,,,
1130,占位符,少一个可选天赋,,0,,,,,,,,,,,,
1131,魔法棒,不知道有什么用……,,2,,,,,,,,,,,,
1 $id name description condition grade status effect:SPR effect:MNY effect:CHR effect:STR effect:INT exclusive[] exclusive[] exclusive[] exclusive[] exclusive[] exclusive[]
2 序号 天赋名 括号中的内容 触发条件 稀有度 初始可用属性点 额外快乐 额外家境 额外颜值 额外体质 额外智力 互斥天赋 互斥天赋 互斥天赋 互斥天赋 互斥天赋 互斥天赋
3 1001 随身玉佩 或许有护佑作用 0
4 1002 红肚兜 小时候死亡率降低 0
5 1003 生而为男 性别一定为男 1 1004 1025 1024 1113
6 1004 生而为女 性别一定为女 1 1003 1024 1025
7 1005 动漫高手 入宅的可能性翻6倍 2
8 1006 乐天派 快乐+1 0 1
9 1007 天赋异禀 初始可用属性点+2 1 2
10 1008 天生抑郁 快乐-3 0 -3
11 1009 网络巨魔 快乐+2 1 2
12 1010 天龙人 你拥有北京户口 2 1012 1013 1014
13 1011 独生子女 你没有兄弟姐妹 0
14 1012 乡间微风 你出生在农村 0 1010 1013 1014
15 1013 城中高楼 你出生在城市 0 1010 1012 1014
16 1014 美籍华人 你有美国国籍 2 3 1010 1012 1013
17 1015 家中老大 你最受父母宠爱 1
18 1016 水性良好 不会被淹死 0
19 1017 先天免疫 你不会得艾滋病 0
20 1018 人类进化 所有属性+1 2 1 1 1 1 1
21 1019 超凡 初始可用属性点+4 2 4
22 1020 父母美貌 颜值+2 1 2
23 1021 红颜薄命 颜值+2,体质-2 0 2 -2
24 1022 属蛇 不会被蛇咬死 0
25 1023 半神 所有属性+2 3 2 2 2 2 2
26 1024 人中龙凤 天生双重性别 2 1003 1004 1025
27 1025 阴阳之外 天生无性别 2 1003 1024 1004 1113
28 1026 彩虹之下 可能和同性交往 0 1113
29 1027 斩情证道 终生不恋爱结婚 1 1113
30 1028 桃花连连 恋爱机会提升 0
31 1029 平安童年 12岁前父母都健在 1
32 1030 宠物大师 宠物不会意外死亡 0
33 1031 天生残疾 体质-2 0 -2
34 1032 早产儿 所有属性-1 0 -1 -1 -1 -1 -1
35 1033 十死无生 体质-10 0 -10
36 1034 家运不顺 家境-2 0 -2
37 1035 头着地 智力-2 0 -2
38 1036 胎教 智力+1 0 1
39 1037 班中红人 和同学容易处好关系 0
40 1038 骑士 能轻松学会骑车 0
41 1039 永远的神 电竞天才 1
42 1040 戒律 赌毒不沾 0
43 1041 丁克 不生孩子 1 1113
44 1042 少数民族 高考+5分 0
45 1043 老司机 你和家人不会发生车祸 0
46 1044 低压 你的家人不会心脏病 0
47 1045 战功 你退伍后会当官 0
48 1046 不孕不育 你生不出孩子 1 1113
49 1047 白头偕老 爱人至少能活到70岁 1
50 1048 神秘的小盒子 100岁时才能开启 3
51 1049 三十而立 30岁时家境+2 AGE?[30] 0 2
52 1050 四十不惑 40岁时智力+2 AGE?[40] 0 2
53 1051 知天命 50岁时智力、快乐+1 AGE?[50] 0 1 1
54 1052 耳顺 60岁时快乐+2 AGE?[60] 0 2
55 1053 从心所欲 70岁时快乐+3 AGE?[70] 0 3
56 1054 老当益壮 60岁时体质+2 AGE?[60] 1 2
57 1055 鹤发童颜 70岁时颜值+3 AGE?[70] 0 3
58 1056 学前启蒙 5岁时智力+2 AGE?[5] 1 2
59 1057 十八变 18岁时颜值+2 AGE?[18] 1 2
60 1058 迟来之财 90岁时家境+4 AGE?[90] 0 4
61 1059 理财达人 30、40、50岁时家境+1 AGE?[30,40,50] 0 1
62 1060 成熟 12、18岁时智力+1 AGE?[12,18] 1 1
63 1061 形象管理 16、24岁时颜值+1 AGE?[16,24] 1 1
64 1062 成年礼 18岁时快乐+1 AGE?[18] 0 1
65 1063 开光之胎 初始可用属性点+1 0 1
66 1064 天命 初始可用属性点+8 3 8
67 1065 祖传药丸 功能不明 1
68 1066 精准扶贫 家境为0时家境+1 (MNY<1)&(MNY>-1) 0 1
69 1067 乐天派 快乐为0时快乐+1 (SPR<1)&(SPR>-1) 1 1
70 1068 命悬一线 体质为0时体质+1 (STR<1)&(STR>-1) 0 1
71 1069 智可生财 若20岁时智力>8,家境+2 (AGE?[20])&(INT>8) 0 2
72 1070 舔狗甚多 若20岁时颜值>8,快乐+2 (AGE?[20])&(CHR>8) 0 2
73 1071 保胎丸 你不会胎死腹中 0
74 1072 白化病 你不会遭遇枪击 0
75 1073 佛宗 考上哈佛大学的几率提高 0
76 1074 悟道 智力>10时快乐+3 INT>10 1 3
77 1075 驻颜 体质>10时颜值+3 STR>10 0 3
78 1076 界限突破 体质>10时快乐+3 STR>10 1 3
79 1077 倾城 颜值>10时快乐+3 CHR>10 1 3
80 1078 训练有方 智力>10时体质+3 INT>10 0 3
81 1079 相由心生 智力>10时颜值+3 INT>10 0 3
82 1080 智多鑫 智力>10时家境+3 INT>10 0 3
83 1081 灵光 快乐>10时其他属性+1 SPR>10 0 1 1 1 1
84 1082 天启 快乐>10时其他属性+2 SPR>10 1 2 2 2 2
85 1083 神谕 快乐>10时其他属性+3 SPR>10 2 3 3 3 3
86 1084 献祭 初始可用属性点-2,快乐+2 0 -2 2 1122
87 1085 幸运儿 初始可用属性点-3,快乐+5 1 -3 5 1122
88 1086 挑战者 初始可用点-10 0 -10 1122
89 1087 你不懂 家境>10时快乐+3 MNY>10 1 3
90 1088 整容 家境>10时颜值+3 MNY>10 0 3
91 1089 钻石健身卡 家境>10时体质+3 MNY>10 0 3
92 1090 身残志坚 体质<0时其他属性+1 STR<0 0 1 1 1 1
93 1091 活死人 体质<-1时其他属性+2 STR<0 1 2 2 2 2
94 1092 开一扇窗 颜值<0时其他属性+1 CHR<0 0 1 1 1 1
95 1093 大额头 颜值-2,智力+2 0 -2 2
96 1094 痘痘脸 颜值-1 0 -1
97 1095 潜能 家境<0时其他属性+1 MNY<0 0 1 1 1 1
98 1096 哀兵 快乐<0时其他属性+1 SPR<0 0 1 1 1 1
99 1097 苦痛侍僧 快乐<-1时其他属性+2 SPR<-1 1 2 2 2 2
100 1098 觉醒 家境<-1时其他属性+2 MNY<-1 1 2 2 2 2
101 1099 抖M 家境-2,快乐+2 0 2 -2
102 1100 海的女儿 颜值-2,初始可用属性点+1 0 1 -2
103 1101 进阶 所有属性>5时,所有属性+1 (SPR>5)&(MNY>5)&(CHR>5)&(STR>5)&(INT>5) 0 1 1 1 1 1
104 1102 超进化 所有属性>5时,所有属性+2 (SPR>5)&(MNY>5)&(CHR>5)&(STR>5)&(INT>5) 1 2 2 2 2 2
105 1103 白色胶囊 你10岁时无事发生 AGE?[10] 0
106 1104 紫色胶囊 跳过你的40~50岁 AGE?[40,41,42,43,44,45,46,47,48,49,50] 2
107 1105 蓝色胶囊 你20、30岁时无事发生 AGE?[20,30] 1
108 1106 健康饮食 你不吃洋快餐 0
109 1107 不想罢了 你不会上清华大学 0
110 1108 挑衅 你喜欢没事找事 0
111 1109 旅行者 你喜欢旅游 0
112 1110 水仙 你比较自恋 0
113 1111 缺一门 无效果 0
114 1112 异界来客 你可能听到一些绝密消息 2
115 1113 三胎人生 你尽可能生三胎 1 1003 1025 1026 1027 1041 1046
116 1114 橙色胶囊 跳过你的60~90岁 AGE?[60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90] 3
117 1115 宙斯 参加奥赛的几率提高 0
118 1116 为人民服务 考公务员时一定能考上 0
119 1117 表现良好 入狱会减刑 0
120 1118 小吉 运气稍微提升 0
121 1119 天秤座 据说做事很公平 0
122 1120 万里挑一 你很攻 0
123 1121 把握不住 你有强迫症 0
124 1122 急了急了 赶着投胎,不要初始属性了 1 -20 1084 1085 1086
125 1123 不离不弃 你不会离婚 0
126 1124 足量 身高不矮 0
127 1125 易胖体质 颜值更容易降低 0
128 1126 黄帝 种族主义者 0
129 1127 左撇子 习惯使用左手 0
130 1128 克苏鲁 &▓▓▓◆▓▓▓¥#▓@■.◆ 2
131 1129 不连续存在 你还拥有其他人格 2
132 1130 占位符 少一个可选天赋 0
133 1131 魔法棒 不知道有什么用…… 2
-1173
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
import { defineConfig } from 'eslint/config'
import parser from '@typescript-eslint/parser'
import plugin from '@typescript-eslint/eslint-plugin'
export default defineConfig([
{
ignores: ['dist/**', 'node_modules/**'],
files: ['**/*.{js,jsx,ts,tsx}'],
},
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
// @ts-ignore
plugins: { '@typescript-eslint': plugin },
},
])
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

-5
View File
@@ -1,5 +0,0 @@
# Life Restart
やり直すんだ。そして、次はうまくやる。[RESTART](view/index.html)
<iframe src="https://discord.com/widget?id=883382868427014255&theme=dark" width="350" height="500" allowtransparency="true" frameborder="0" sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"></iframe>
-2
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
+46 -21
View File
@@ -1,23 +1,48 @@
{
"name": "life_restart",
"type": "module",
"bin": "repl/index.js",
"scripts": {
"test": "node test",
"xlsxTransform": "node utils/xlsxTransform.js data",
"dev": "webpack serve --open /view/index.html",
"build": "webpack --mode production"
},
"dependencies": {
"@babel/core": "^7.15.4",
"@babel/preset-env": "^7.15.4",
"babel-loader": "^8.2.2",
"core-js": "^3.17.2",
"webpack": "^5.51.2",
"webpack-dev-server": "^4.1.0",
"xlsx": "^0.17.0"
},
"devDependencies": {
"webpack-cli": "^4.8.0"
}
"name": "remake",
"type": "module",
"version": "3.0.0",
"author": "Vick Scarlet <vick@syaro.io>",
"scripts": {
"dev": "pnpm --filter @remake/web dev",
"dev:prod": "pnpm --filter @remake/web dev:prod",
"build:web": "pnpm --filter @remake/web build",
"build:data": "pnpm --filter @remake/data build",
"preview": "pnpm --filter @remake/web preview",
"start": "pnpm --filter @remake/console start",
"lint": "bunx --bun eslint",
"lint:console": "pnpm --filter @remake/console lint",
"lint:web": "pnpm --filter @remake/web lint",
"lint:core": "pnpm --filter @remake/core lint",
"lint:data": "pnpm --filter @remake/data lint",
"lint:condition": "pnpm --filter @remake/condition lint",
"lint:hooks": "pnpm --filter @remake/hooks lint",
"fix": "bunx --bun eslint --fix",
"fix:console": "pnpm --filter @remake/console lint --fix",
"fix:web": "pnpm --filter @remake/web lint --fix",
"fix:core": "pnpm --filter @remake/core lint --fix",
"fix:data": "pnpm --filter @remake/data lint --fix",
"fix:condition": "pnpm --filter @remake/condition lint --fix",
"fix:hooks": "pnpm --filter @remake/hooks lint --fix",
"test": "pnpm --filter @remake/web test",
"test:console": "pnpm --filter @remake/console test",
"test:web": "pnpm --filter @remake/web test",
"test:core": "pnpm --filter @remake/core test",
"test:data": "pnpm --filter @remake/data test",
"test:condition": "pnpm --filter @remake/condition test",
"test:hooks": "pnpm --filter @remake/hooks test"
},
"devDependencies": {
"@types/bun": "1.3.14",
"@typescript-eslint/eslint-plugin": "^8.67.0",
"@typescript-eslint/parser": "^8.67.0",
"@typescript/native": "npm:typescript@^7.0.2",
"eslint": "^10.8.1",
"typescript": "npm:@typescript/typescript6@^6.0.2"
},
"workspaces": [
"packages/*",
"apps/*",
"thirdparty/*"
]
}
+118
View File
@@ -0,0 +1,118 @@
import { expect, test, describe } from 'bun:test'
import { check } from '.'
// 定义属性 mock 字典的类型契约
interface MockProperties {
[key: string]: number | number[]
}
/**
* 属性注入高阶函数:模拟角色属性管理类
*/
function withProp(prop: MockProperties) {
return (condition: string): boolean => check(prop, condition)
}
describe('condition', () => {
// 实例化一个带满各种脏数据(数字、数组、空数组)的强类型测试代理
const check = withProp({
n1: 0,
n2: -10,
n3: 10,
nl1: [1, 2, 3],
nl2: [0],
nl3: [],
})
test('gt(>)', () => {
expect(check('n3>11')).toBe(false)
expect(check('n3>10')).toBe(false)
expect(check('n3>9')).toBe(true)
})
test('gte(>=)', () => {
expect(check('n3>=11')).toBe(false)
expect(check('n3>=10')).toBe(true)
expect(check('n3>=9')).toBe(true)
})
test('lt(<)', () => {
expect(check('n3<9')).toBe(false)
expect(check('n3<10')).toBe(false)
expect(check('n3<11')).toBe(true)
})
test('lte(<=)', () => {
expect(check('n3<=9')).toBe(false)
expect(check('n3<=10')).toBe(true)
expect(check('n3<=11')).toBe(true)
})
test('eq(=)', () => {
expect(check('n3=9')).toBe(false)
expect(check('n3=10')).toBe(true)
expect(check('n3=11')).toBe(false)
expect(check('nl1=0')).toBe(false)
expect(check('nl1=1')).toBe(true)
expect(check('nl1=2')).toBe(true)
expect(check('nl1=3')).toBe(true)
})
test('ne(!=)', () => {
expect(check('n3!=9')).toBe(true)
expect(check('n3!=10')).toBe(false)
expect(check('n3!=11')).toBe(true)
expect(check('nl1!=0')).toBe(true)
expect(check('nl1!=1')).toBe(false)
expect(check('nl1!=2')).toBe(false)
expect(check('nl1!=3')).toBe(false)
})
test('in(?)', () => {
expect(check('n3?[1,2,3]')).toBe(false)
expect(check('n3?[10,11,12]')).toBe(true)
expect(check('nl1?[0,1]')).toBe(true)
expect(check('nl2?[1,2,3]')).toBe(false)
expect(check('nl3?[1,2,3]')).toBe(false)
expect(check('nl2?[]')).toBe(false)
expect(check('nl3?[]')).toBe(false)
})
test('notin(!)', () => {
expect(check('n3![1,2,3]')).toBe(true)
expect(check('n3![10,11,12]')).toBe(false)
expect(check('nl1![0,1]')).toBe(false)
expect(check('nl2![1,2,3]')).toBe(true)
expect(check('nl3![1,2,3]')).toBe(true)
expect(check('nl2![]')).toBe(true)
expect(check('nl3![]')).toBe(true)
})
test('and(&)', () => {
expect(check('n1>=0&n2<0')).toBe(true)
expect(check('n2>0&n3>0')).toBe(false)
expect(check('n2<0&n3<0')).toBe(false)
expect(check('n2<0&n3>0')).toBe(true)
expect(check('n1=0&n2<0&n3>0')).toBe(true)
expect(check('n1=0&n2>0&n3>0')).toBe(false)
})
test('or(|)', () => {
expect(check('n1>=0|n2<0')).toBe(true)
expect(check('n2>0|n3>0')).toBe(true)
expect(check('n2<0|n3<0')).toBe(true)
expect(check('n2<0|n3>0')).toBe(true)
expect(check('n2>0|n3<0')).toBe(false)
expect(check('n1=0|n2<0|n3>0')).toBe(true)
expect(check('n1=0|n2>0|n3>0')).toBe(true)
expect(check('n1!=0|n2>0|n3<0')).toBe(false)
})
test('mix', () => {
expect(check('(n1=0|n2<0|n3>0)&(n1=0|n2>0|n3>0)')).toBe(true)
expect(check('(n1=0|n2<0|n3>0)&(n1!=0|n2>0|n3<0)')).toBe(false)
expect(check('n1=0|n2<0|n3>0&n1!=0|n2>0|n3<0')).toBe(true)
expect(check('(n1>0|n1?[-10,0])&(n2>0|n3![0,1])')).toBe(true)
expect(check('(n1>0&n1?[-10,0])|(n2<0&n3![0,1])')).toBe(true)
})
})
+190
View File
@@ -0,0 +1,190 @@
export type ConditionNode = string | ConditionTree
export interface ConditionTree extends Array<ConditionNode> {}
export type PropertyContainer = Record<string, any>
export function parse(condition: string): ConditionTree {
const conditions: ConditionTree = []
const length = condition.length
const stack: ConditionTree[] = []
stack.unshift(conditions)
let cursor = 0
const catchString = (i: number): void => {
const str = condition.substring(cursor, i).trim()
cursor = i
if (str) stack[0]?.push(str)
}
for (let i = 0; i < length; i++) {
switch (condition[i]) {
case ' ':
continue
case '(': {
catchString(i)
cursor++
const sub: ConditionTree = []
stack[0]?.push(sub)
stack.unshift(sub)
break
}
case ')':
catchString(i)
cursor++
stack.shift()
break
case '|':
case '&':
catchString(i)
catchString(i + 1)
break
default:
continue
}
}
catchString(length)
return conditions
}
export function check(property: PropertyContainer, condition: string): boolean {
const conditions = parse(condition)
return checkParsedConditions(property, conditions)
}
/**
* 递归计算已解析出来的多维语法树结果
*/
function checkParsedConditions(
property: PropertyContainer,
conditions: ConditionNode,
): boolean {
// 如果已经剥离到了最底层的纯字符串表达式(如 'AGE>10'),直接移交核心原子判定器
if (!Array.isArray(conditions)) {
return checkProp(property, conditions)
}
if (conditions.length === 0) return true
if (conditions.length === 1) {
return checkParsedConditions(property, conditions[0]!)
}
let ret = checkParsedConditions(property, conditions[0]!)
for (let i = 1; i < conditions.length; i += 2) {
const operator = conditions[i]
const nextNode = conditions[i + 1]
// 防御性拦截,防止表达式残缺或配置错误引发崩溃
if (nextNode === undefined) return false
switch (operator) {
case '&':
if (ret) {
ret = checkParsedConditions(property, nextNode)
}
break
case '|':
if (ret) return true
ret = checkParsedConditions(property, nextNode)
break
default:
return false
}
}
return ret
}
function checkProp(property: PropertyContainer, condition: string): boolean {
const length = condition.length
let i = condition.search(/[><!?=]/)
// 防御处理:如果没有找到任何操作符,视为非法表达式直接拒签
if (i === -1) return false
const prop = condition.substring(0, i)
// 判断是否是双字符操作符(如 >=, <=, !=),若是则向前多吞一个字符位
const isDoubleSymbol = condition[i + 1] === '='
const symbol = condition.substring(i, (i += isDoubleSymbol ? 2 : 1))
const d = condition.substring(i, length)
const propData = property[prop]
const conditionData: number | any[] =
d[0] === '[' ? JSON.parse(d) : Number(d)
switch (symbol) {
case '>':
return (propData ?? 0) > conditionData
case '<':
return (propData ?? 0) < conditionData
case '>=':
return (propData ?? 0) >= conditionData
case '<=':
return (propData ?? 0) <= conditionData
case '=':
if (propData instanceof Set) {
return propData.has(conditionData)
}
if (Array.isArray(propData)) {
return propData.includes(conditionData)
}
return (propData ?? 0) == conditionData
case '!=':
if (propData instanceof Set) {
return !propData.has(conditionData)
}
if (Array.isArray(propData)) {
return !propData.includes(conditionData)
}
return (propData ?? 0) != conditionData
case '?': // 🌟 包含判定符(如 属性值 是否在 [1,2,3] 数组范围内)
if (propData instanceof Set) {
if (Array.isArray(conditionData)) {
for (const c of conditionData) {
if (propData.has(c)) return true
}
}
return false
}
if (Array.isArray(propData)) {
if (Array.isArray(conditionData)) {
for (const p of propData) {
if (conditionData.includes(p)) return true
}
}
return false
}
if (Array.isArray(conditionData)) {
return conditionData.includes(propData)
}
return false
case '!': // 🌟 排除判定符(如 属性值 是否不存在于 [1,2,3] 数组中)
if (propData instanceof Set) {
if (Array.isArray(conditionData)) {
for (const c of conditionData) {
if (propData.has(c)) return false
}
}
return true
}
if (Array.isArray(propData)) {
if (Array.isArray(conditionData)) {
for (const p of propData) {
if (conditionData.includes(p)) return false
}
}
return true
}
if (Array.isArray(conditionData)) {
return !conditionData.includes(propData)
}
return true
default:
return false
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "@remake/condition",
"type": "module",
"version": "3.0.0",
"main": "./index.ts",
"author": "Vick Scarlet <vick@syaro.io>",
"scripts": {
"lint": "bunx --bun eslint .",
"test": "bun test"
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["**/*"]
}
+29
View File
@@ -0,0 +1,29 @@
{
"name": "@remake/core",
"type": "module",
"version": "3.0.0",
"author": "Vick Scarlet <vick@syaro.io>",
"main": "src/index.ts",
"scripts": {
"lint": "bunx --bun eslint .",
"test": "bun test"
},
"dependencies": {
"@remake/condition": "workspace:*",
"@remake/data": "workspace:*",
"@remake/vitex": "workspace:*",
"immer": "^11.1.15"
},
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts",
"default": "./src/index.ts"
},
"./*": {
"types": "./src/*.ts",
"import": "./src/*.ts",
"default": "./src/*.ts"
}
}
}
+39
View File
@@ -0,0 +1,39 @@
import { expect, test, describe } from 'bun:test'
import { createState } from './state'
import { trigger } from './achievement'
import { AchievementOpportunity } from '@remake/data'
import { enableMapSet } from 'immer'
enableMapSet()
describe('Achievement', () => {
const profile = {
times: 0,
talents: new Set([]),
achievements: new Set([]),
events: new Set([]),
}
const allocation = {
charm: 5,
intelligence: 5,
strength: 5,
money: 5,
spirit: 5,
}
test('trigger [times:500]', () => {
const p = { ...profile, times: 500 }
const result = trigger(
AchievementOpportunity.End,
createState(allocation, []),
p,
)
expect(result.state.achievements).toContain(101) // 既视感
expect(result.state.achievements).toContain(102) // 孟婆愁
expect(result.state.achievements).toContain(103) // 所有人都是我
expect(result.state.achievements).toContain(104) // Rewrite
expect(result.triggers).toContain(101)
expect(result.triggers).toContain(102)
expect(result.triggers).toContain(103)
expect(result.triggers).toContain(104)
})
})
+30
View File
@@ -0,0 +1,30 @@
import type { Achievement, AchievementOpportunity } from '@remake/data'
import { achievements } from '@remake/data'
import type { GameState, ProfileState } from './state'
import { createFlatState } from './state'
import { check } from '@remake/condition'
import { produce } from 'immer'
import type { TriggerResult } from './game'
const OpportunityMap = Map.groupBy(
achievements.keys(),
a => achievements.get(a)!.opportunity,
)
export function trigger(
opportunity: AchievementOpportunity,
state: GameState,
profile: ProfileState,
): TriggerResult<Achievement['id']> {
const flatState = createFlatState(state, profile)
const triggers = OpportunityMap.get(opportunity)!.filter(a => {
if (state.achievements.has(a)) return false
if (profile.achievements.has(a)) return false
const { condition } = achievements.get(a)!
return check(flatState, condition)
})
const newState = produce(state, draft => {
draft.achievements = new Set([...draft.achievements, ...triggers])
})
return { state: newState, triggers }
}
+60
View File
@@ -0,0 +1,60 @@
import { expect, test, describe } from 'bun:test'
import { pullChara, uniqueGenerate } from './character'
import { enableMapSet } from 'immer'
enableMapSet()
describe('Character', () => {
const sum = (map: Map<any, number>) =>
Array.from(map.values()).reduce((acc, val) => acc + val, 0)
test('pull', () => {
let result = pullChara(
{ count: 10, knife: 10 },
{ times: 0, drawns: new Map() },
)
expect(result.characters).not.toContainValue(null)
expect(result.characters.length).toBe(10)
expect(result.times.times).toBe(10)
expect(result.times.drawns.size).toBe(10)
result = pullChara({ count: 3, knife: 10 }, result.times)
expect(result.characters).not.toContainValue(null)
expect(result.characters.length).toBe(3)
expect(result.times.times).toBe(13)
expect(sum(result.times.drawns)).toBe(13)
result = pullChara({ count: 3, knife: 10 }, result.times)
expect(result.characters).not.toContainValue(null)
expect(result.characters.length).toBe(3)
expect(result.times.times).toBe(16)
expect(sum(result.times.drawns)).toBe(16)
result = pullChara({ count: 20, knife: 1 }, result.times)
expect(result.characters).not.toContainValue(null)
expect(result.characters.length).toBe(20)
expect(result.times.times).toBe(36)
expect(sum(result.times.drawns)).toBe(36)
})
const wr = (s: number, e: number) => {
const length = e - s + 1
return Array.from(
{ length },
(_, i) => [s + i, Math.min(i + 1, length - i)] as const,
)
}
test('unique', () => {
const unique = uniqueGenerate({ prop: wr(0, 10), talent: wr(1, 5) })
expect(unique.property).toHaveProperty('CHR')
expect(unique.property).toHaveProperty('INT')
expect(unique.property).toHaveProperty('STR')
expect(unique.property).toHaveProperty('MNY')
expect(unique.property.CHR).toBeGreaterThanOrEqual(0)
expect(unique.property.INT).toBeGreaterThanOrEqual(0)
expect(unique.property.STR).toBeGreaterThanOrEqual(0)
expect(unique.property.MNY).toBeGreaterThanOrEqual(0)
expect(unique.talent.length).toBeGreaterThanOrEqual(0)
expect(unique.property.CHR).toBeLessThanOrEqual(10)
expect(unique.property.INT).toBeLessThanOrEqual(10)
expect(unique.property.STR).toBeLessThanOrEqual(10)
expect(unique.property.MNY).toBeLessThanOrEqual(10)
expect(unique.talent.length).toBeLessThanOrEqual(5)
})
})

Some files were not shown because too many files have changed in this diff Show More