@jjlmoya/utils-tabletop 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/entries.ts +8 -0
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +1 -1
- package/src/tool/decision-wheel/client.ts +2 -0
- package/src/tool/decision-wheel/components/SegmentEditor.astro +1 -1
- package/src/tool/decision-wheel/decision-wheel.css +23 -7
- package/src/tool/dice-roller-simulator/component.astro +116 -26
- package/src/tool/hidden-role-dealer/bibliography.astro +16 -0
- package/src/tool/hidden-role-dealer/bibliography.ts +12 -0
- package/src/tool/hidden-role-dealer/client.ts +265 -0
- package/src/tool/hidden-role-dealer/component.astro +184 -0
- package/src/tool/hidden-role-dealer/entry.ts +145 -0
- package/src/tool/hidden-role-dealer/hidden-role-dealer.css +604 -0
- package/src/tool/hidden-role-dealer/i18n/de.ts +195 -0
- package/src/tool/hidden-role-dealer/i18n/en.ts +279 -0
- package/src/tool/hidden-role-dealer/i18n/es.ts +279 -0
- package/src/tool/hidden-role-dealer/i18n/fr.ts +215 -0
- package/src/tool/hidden-role-dealer/i18n/id.ts +191 -0
- package/src/tool/hidden-role-dealer/i18n/it.ts +191 -0
- package/src/tool/hidden-role-dealer/i18n/ja.ts +191 -0
- package/src/tool/hidden-role-dealer/i18n/ko.ts +191 -0
- package/src/tool/hidden-role-dealer/i18n/nl.ts +191 -0
- package/src/tool/hidden-role-dealer/i18n/pl.ts +191 -0
- package/src/tool/hidden-role-dealer/i18n/pt.ts +191 -0
- package/src/tool/hidden-role-dealer/i18n/ru.ts +191 -0
- package/src/tool/hidden-role-dealer/i18n/sv.ts +191 -0
- package/src/tool/hidden-role-dealer/i18n/tr.ts +191 -0
- package/src/tool/hidden-role-dealer/i18n/zh.ts +191 -0
- package/src/tool/hidden-role-dealer/index.ts +10 -0
- package/src/tool/hidden-role-dealer/logic.test.ts +99 -0
- package/src/tool/hidden-role-dealer/logic.ts +134 -0
- package/src/tool/hidden-role-dealer/modules/custom-role-manager.ts +35 -0
- package/src/tool/hidden-role-dealer/modules/dealer-logic.ts +73 -0
- package/src/tool/hidden-role-dealer/modules/hold-controller.ts +38 -0
- package/src/tool/hidden-role-dealer/modules/particle-effects.ts +40 -0
- package/src/tool/hidden-role-dealer/modules/player-manager.ts +44 -0
- package/src/tool/hidden-role-dealer/modules/presets.ts +46 -0
- package/src/tool/hidden-role-dealer/modules/setup-handlers.ts +34 -0
- package/src/tool/hidden-role-dealer/modules/storage.ts +23 -0
- package/src/tool/hidden-role-dealer/modules/utils.ts +18 -0
- package/src/tool/hidden-role-dealer/modules/wizard-html.ts +161 -0
- package/src/tool/hidden-role-dealer/modules/wizard-renderer.ts +161 -0
- package/src/tool/hidden-role-dealer/modules/writer-handler.ts +70 -0
- package/src/tool/hidden-role-dealer/seo.astro +16 -0
- package/src/tool/hidden-role-dealer/types.ts +31 -0
- package/src/tool/scatter-direction-selector/animation.ts +278 -0
- package/src/tool/scatter-direction-selector/bibliography.astro +16 -0
- package/src/tool/scatter-direction-selector/bibliography.ts +12 -0
- package/src/tool/scatter-direction-selector/client.ts +296 -0
- package/src/tool/scatter-direction-selector/component.astro +184 -0
- package/src/tool/scatter-direction-selector/entry.ts +59 -0
- package/src/tool/scatter-direction-selector/history.ts +71 -0
- package/src/tool/scatter-direction-selector/i18n/de.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/en.ts +231 -0
- package/src/tool/scatter-direction-selector/i18n/es.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/fr.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/id.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/it.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/ja.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/ko.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/nl.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/pl.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/pt.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/ru.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/sv.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/tr.ts +143 -0
- package/src/tool/scatter-direction-selector/i18n/zh.ts +143 -0
- package/src/tool/scatter-direction-selector/index.ts +10 -0
- package/src/tool/scatter-direction-selector/logic.test.ts +62 -0
- package/src/tool/scatter-direction-selector/logic.ts +52 -0
- package/src/tool/scatter-direction-selector/scatter-direction-selector.css +794 -0
- package/src/tool/scatter-direction-selector/seo.astro +16 -0
- package/src/tool/scatter-direction-selector/settings.ts +81 -0
- package/src/tool/scatter-direction-selector/sound.ts +22 -0
- package/src/tools.ts +4 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { bibliography } from '../bibliography';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { HiddenRoleDealerUI } from '../entry';
|
|
4
|
+
|
|
5
|
+
const ui: HiddenRoleDealerUI = {
|
|
6
|
+
title: 'Gizli Rol Dağıtıcı',
|
|
7
|
+
setupTitle: 'Rol Dağıtım Kurulumu',
|
|
8
|
+
playerList: 'Oyuncu Listesi',
|
|
9
|
+
addPlayer: 'Ekle',
|
|
10
|
+
addPlayerPlaceholder: 'Oyuncu adı girin...',
|
|
11
|
+
rolePool: 'Gizli Rol Havuzu',
|
|
12
|
+
presetsTitle: 'Oyun Şablonları',
|
|
13
|
+
presetCustom: 'Özel Kurulum',
|
|
14
|
+
dealRoles: 'Gizli Rolleri Dağıt',
|
|
15
|
+
clearPlayers: 'Tümünü Temizle',
|
|
16
|
+
instructions: 'Oyuncu adlarını girin, gizli rolleri yapılandırın ve Gizli Rolleri Dağıt\'a dokunun.',
|
|
17
|
+
passTo: 'Cihazı şu oyuncuya verin:',
|
|
18
|
+
holdToReveal: 'Göster seçeneği için basılı tutun',
|
|
19
|
+
releasingResets: 'Göstermek için basılı tutmaya devam edin...',
|
|
20
|
+
hideRole: 'Anlaşıldı, rolü gizle',
|
|
21
|
+
nextPlayer: 'Sıradaki Oyuncu',
|
|
22
|
+
finishedTitle: 'Tüm Roller Dağıtıldı',
|
|
23
|
+
finishedDesc: 'Tüm oyuncular gizlice rollerine baktı. Artık oyunu başlatabilirsiniz.',
|
|
24
|
+
restart: 'Yeniden Başlat ve Dağıt',
|
|
25
|
+
rolesCount: 'Seçilen Roller',
|
|
26
|
+
playersCount: 'Eklenen Oyuncular',
|
|
27
|
+
mismatchWarning: 'Rolleri dağıtmak için en az 3 oyuncu gereklidir.',
|
|
28
|
+
werewolfName: 'Kurt Adam / Vampir',
|
|
29
|
+
werewolfDesc: 'Köylüler gizli Kurt Adamları bulmaya ve oylama ile elemeye çalışır.',
|
|
30
|
+
avalonName: 'The Resistance: Avalon',
|
|
31
|
+
avalonDesc: 'Merlin ve sadık hizmetkarlar sabote edilmekten kaçınarak görevleri tamamlar.',
|
|
32
|
+
hitlerName: 'Secret Hitler',
|
|
33
|
+
hitlerDesc: 'Liberaller yasaları geçirmeye ve gizli lideri bulmaya çalışır.',
|
|
34
|
+
customName: 'Özel Kurulum',
|
|
35
|
+
customDesc: 'Oyunculara basit etiketli kartlar dağıtın.',
|
|
36
|
+
roleWerewolfName: 'Kurt Adam',
|
|
37
|
+
roleWerewolfDesc: 'Geceleri gizlice Köylüleri eleyin.',
|
|
38
|
+
roleWerewolfSecret: 'Köyü kandırmak için diğer Kurt Adamlarla birlikte çalışın.',
|
|
39
|
+
roleSeerName: 'Kahin',
|
|
40
|
+
roleSeerDesc: 'Her gece bir oyuncunun tarafını kontrol edin.',
|
|
41
|
+
roleSeerSecret: 'Kurt Adamları belirleyin ancak hayatta kalmak için gizli kalın.',
|
|
42
|
+
roleDoctorName: 'Doktor',
|
|
43
|
+
roleDoctorDesc: 'Her gece bir oyuncuyu elenmekten koruyun.',
|
|
44
|
+
roleDoctorSecret: 'Kritik köylüleri hayatta tutun.',
|
|
45
|
+
roleVillagerName: 'Köylü',
|
|
46
|
+
roleVillagerDesc: 'Gizli Kurt Adamları bulun.',
|
|
47
|
+
roleVillagerSecret: 'Tartışın, analiz edin ve şüpheli oyunculara oy verin.',
|
|
48
|
+
roleMerlinName: 'Merlin',
|
|
49
|
+
roleMerlinDesc: 'Kötülüğün ajanlarını bilir.',
|
|
50
|
+
roleMerlinSecret: 'Kendinizi belli etmeden takımınızı zafere ulaştırın.',
|
|
51
|
+
roleAssassinName: 'Suikastçı',
|
|
52
|
+
roleAssassinDesc: 'Oyun sonunda Merlin\'e suikast düzenleyebilir.',
|
|
53
|
+
roleAssassinSecret: 'Sonunda Merlin\'i bularak zaferi çalın.',
|
|
54
|
+
roleMinionName: 'Mordred\'in Hizmetkarı',
|
|
55
|
+
roleMinionDesc: 'Mordred\'in bilgili ajanı.',
|
|
56
|
+
roleMinionSecret: 'Fark edilmeden görevleri sabote edin.',
|
|
57
|
+
roleServantName: 'Sadık Hizmetkar',
|
|
58
|
+
roleServantDesc: 'Arthur için görevleri tamamlayın.',
|
|
59
|
+
roleServantSecret: 'Tartışın, takım önerin ve mantıklı oy kullanın.',
|
|
60
|
+
roleHitlerName: 'Hitler',
|
|
61
|
+
roleHitlerDesc: 'Faşistlerin gizli lideri.',
|
|
62
|
+
roleHitlerSecret: 'Şansölye seçilin veya Faşistlerin yasaları geçirmesine izin verin.',
|
|
63
|
+
roleFascistName: 'Faşist',
|
|
64
|
+
roleFascistDesc: 'Faşist yasaları geçirmek için koordinasyon sağlayın.',
|
|
65
|
+
roleFascistSecret: 'Hitler\'i koruyun ve seçim sonuçlarını manipüle edin.',
|
|
66
|
+
roleLiberalName: 'Liberal',
|
|
67
|
+
roleLiberalDesc: 'Faşist tehdidi bulun ve durdurun.',
|
|
68
|
+
roleLiberalSecret: 'Liberal yasaları geçirin ve mümkünse Hitler\'i eleyin.',
|
|
69
|
+
roleCustomName: 'Özel Rol',
|
|
70
|
+
roleCustomDesc: 'Özel rolün genel açıklaması.',
|
|
71
|
+
roleCustomSecret: 'Takım hedefinizi gerçekleştirin.',
|
|
72
|
+
teamWerewolves: 'Kurt Adamlar',
|
|
73
|
+
teamVillagers: 'Köylüler',
|
|
74
|
+
teamGood: 'İyi',
|
|
75
|
+
teamEvil: 'Kötü',
|
|
76
|
+
teamFascist: 'Faşist',
|
|
77
|
+
teamLiberal: 'Liberal',
|
|
78
|
+
teamCustom: 'Özel Takım',
|
|
79
|
+
secretRoleIs: 'gizli rolünüz:',
|
|
80
|
+
customRolesTitle: 'Özel Rol Kurulumu',
|
|
81
|
+
customRolePlaceholder: 'Rol adı girin...',
|
|
82
|
+
neutralLabel: 'Tarafsız',
|
|
83
|
+
goodLabel: 'İyi',
|
|
84
|
+
evilLabel: 'Kötü',
|
|
85
|
+
customMismatchWarning: 'Oyuncu sayısı kadar özel rol yapılandırın.',
|
|
86
|
+
impostorPresetName: 'Aramızdaki Hain (Impostor)',
|
|
87
|
+
impostorPresetDesc: 'Özelleştirilebilir sayılarla Hain ve Mürettebat rolleri atayın.',
|
|
88
|
+
impostorTitle: 'Hain Ayarları',
|
|
89
|
+
impostorWarning: 'Hainlerden daha fazla oyuncuya ihtiyacınız var.',
|
|
90
|
+
roleImpostorName: 'Hain',
|
|
91
|
+
roleImpostorDesc: 'Mürettebat üyelerini gizlice eleyin.',
|
|
92
|
+
roleImpostorSecret: 'Yakalanmadan sabote edin ve eleyin.',
|
|
93
|
+
roleCrewmateName: 'Mürettebat',
|
|
94
|
+
roleCrewmateDesc: 'Görevleri tamamlayın ve Hain\'i bulun.',
|
|
95
|
+
roleCrewmateSecret: 'Gizli kelime:',
|
|
96
|
+
teamImpostor: 'Hain',
|
|
97
|
+
teamCrewmate: 'Mürettebat',
|
|
98
|
+
impostorSharedSecretLabel: 'Ortak Gizli Kelime',
|
|
99
|
+
impostorSharedSecretPlaceholder: 'örn. Yer: Restoran veya Kelime: Elma',
|
|
100
|
+
impostorModeLabel: 'Hain Hesaplama Modu',
|
|
101
|
+
impostorModeFixed: 'Sabit Sayı',
|
|
102
|
+
impostorModePercentage: 'Yüzde (%)',
|
|
103
|
+
impostorModeRange: 'Rastgele Aralık',
|
|
104
|
+
impostorFixedLabel: 'Hain Sayısı',
|
|
105
|
+
impostorPercentageLabel: 'Hain Yüzdesi (%)',
|
|
106
|
+
impostorRangeMinLabel: 'Minimum Hain',
|
|
107
|
+
impostorRangeMaxLabel: 'Maximum Hain',
|
|
108
|
+
impostorWriterLabel: 'Kelime Yazarı / Host',
|
|
109
|
+
impostorWriterRandom: 'Oyuncu olmayan sunucu',
|
|
110
|
+
roleWriterName: 'Yazar',
|
|
111
|
+
roleWriterDesc: 'Gizli kelimeyi siz belirlersiniz.',
|
|
112
|
+
writerInputPrompt: 'Yazarsınız. Bu oyun için gizli kelimeyi veya yeri girin:',
|
|
113
|
+
writerInputPlaceholder: 'örn. Restoran, Hastane...',
|
|
114
|
+
btnSetWord: 'Kelimeyi Kaydet ve Başlat',
|
|
115
|
+
impostorWriterPreround: 'Gizli Ön Tur (Tüm oyuncular)',
|
|
116
|
+
impostorWriterDescHost: 'Oyuncu olmayan sunucu rastgele kelime kullanır.',
|
|
117
|
+
impostorWriterDescPreround: 'Ön tur, tüm oyuncuların gizlice kelimeler yazmasını ve ardından rastgele birini seçmesini sağlar.',
|
|
118
|
+
impostorWriterDescPlayer: 'Seçilen oyuncu kelimeyi yazar ve hain olamaz.',
|
|
119
|
+
preroundPassTitle: 'Ön tur: Cihazı şu oyuncuya verin:',
|
|
120
|
+
preroundInputPrompt: 'Gizlice bir kelime veya yer yazın (ekrandan gizlenir):',
|
|
121
|
+
preroundPassHint: 'Kelimeyi diğer oyunculardan gizli tutun!',
|
|
122
|
+
btnSubmitWord: 'Kelimeyi Gönder',
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const faq = [
|
|
126
|
+
{
|
|
127
|
+
question: 'Gizli Rol Dağıtıcı nasıl çalışır?',
|
|
128
|
+
answer: 'Bu araç, sosyal çıkarım oyunları için gizli rolleri tek bir cihazda yerel olarak dağıtmanızı sağlar. Oyuncu adlarını girer, şablonu seçer ve cihazı sırayla oyunculara iletirsiniz. Her oyuncu kendi rolünü gizlice görmek için göster butonunu basılı tutar.',
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
const howTo = [
|
|
133
|
+
{
|
|
134
|
+
name: 'Oyuncuları Ekle',
|
|
135
|
+
text: 'Katılımcıların adlarını kurulum paneline girin.',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'Şablon Seç',
|
|
139
|
+
text: 'Hazır oyun kurallarından birini seçin veya özel bir rol havuzu oluşturun.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'Geç ve Göster',
|
|
143
|
+
text: 'Cihazı sırayla her oyuncuya verin. Her oyuncu gizli rolünü görmek için göster düğmesini basılı tutmalı, ardından sonrakine tıklamalıdır.',
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
export const content: ToolLocaleContent<HiddenRoleDealerUI> = {
|
|
148
|
+
slug: 'gizli-rol-dagitici-roller',
|
|
149
|
+
title: 'Gizli Rol Dağıtıcı: Oyun Kartı Dağıtıcısı & Kimlik Dağıtıcı',
|
|
150
|
+
description: 'Kurt Adam, Avalon, Secret Hitler ve Hain gibi sosyal çıkarım oyunları için gizli rolleri fiziksel kartlar olmadan dağıtın.',
|
|
151
|
+
ui,
|
|
152
|
+
seo: [
|
|
153
|
+
{ type: 'title', text: 'Sosyal Çıkarım Oyunlarında Takım Dengesi', level: 2 },
|
|
154
|
+
{ type: 'paragraph', html: 'İyi ve kötü rollerin oranını doğru şekilde dengelemek adil ve heyecan verici bir oyun deneyimi için temeldir.' },
|
|
155
|
+
],
|
|
156
|
+
faq,
|
|
157
|
+
bibliography,
|
|
158
|
+
howTo,
|
|
159
|
+
schemas: [
|
|
160
|
+
{
|
|
161
|
+
'@context': 'https://schema.org',
|
|
162
|
+
'@type': 'FAQPage',
|
|
163
|
+
'mainEntity': faq.map((f) => ({
|
|
164
|
+
'@type': 'Question',
|
|
165
|
+
'name': f.question,
|
|
166
|
+
'acceptedAnswer': {
|
|
167
|
+
'@type': 'Answer',
|
|
168
|
+
'text': f.answer,
|
|
169
|
+
},
|
|
170
|
+
})),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
'@context': 'https://schema.org',
|
|
174
|
+
'@type': 'SoftwareApplication',
|
|
175
|
+
'name': ui.title,
|
|
176
|
+
'operatingSystem': 'All',
|
|
177
|
+
'applicationCategory': 'UtilitiesApplication',
|
|
178
|
+
'browserRequirements': 'Requires HTML5. Requires JavaScript.',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
'@context': 'https://schema.org',
|
|
182
|
+
'@type': 'HowTo',
|
|
183
|
+
'name': ui.setupTitle,
|
|
184
|
+
'step': howTo.map((h) => ({
|
|
185
|
+
'@type': 'HowToStep',
|
|
186
|
+
'name': h.name,
|
|
187
|
+
'text': h.text,
|
|
188
|
+
})),
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { bibliography } from '../bibliography';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { HiddenRoleDealerUI } from '../entry';
|
|
4
|
+
|
|
5
|
+
const ui: HiddenRoleDealerUI = {
|
|
6
|
+
title: '秘密身份分发器',
|
|
7
|
+
setupTitle: '身份分配设置',
|
|
8
|
+
playerList: '玩家列表',
|
|
9
|
+
addPlayer: '添加',
|
|
10
|
+
addPlayerPlaceholder: '输入玩家姓名...',
|
|
11
|
+
rolePool: '秘密身份池',
|
|
12
|
+
presetsTitle: '游戏模版',
|
|
13
|
+
presetCustom: '自定义设置',
|
|
14
|
+
dealRoles: '分发秘密身份',
|
|
15
|
+
clearPlayers: '清空玩家',
|
|
16
|
+
instructions: '输入玩家名称,配置秘密身份,然后点击“分发秘密身份”。',
|
|
17
|
+
passTo: '将设备递给',
|
|
18
|
+
holdToReveal: '按住以显示',
|
|
19
|
+
releasingResets: '继续按住以显示...',
|
|
20
|
+
hideRole: '已确认,隐藏身份',
|
|
21
|
+
nextPlayer: '下一位玩家',
|
|
22
|
+
finishedTitle: '身份全部分发完毕',
|
|
23
|
+
finishedDesc: '所有玩家已秘密确认自己的身份。现在可以开始游戏了。',
|
|
24
|
+
restart: '重新开始并分配',
|
|
25
|
+
rolesCount: '已选身份数',
|
|
26
|
+
playersCount: '已加玩家数',
|
|
27
|
+
mismatchWarning: '分发身份至少需要 3 名玩家。',
|
|
28
|
+
werewolfName: '狼人杀 / 杀手',
|
|
29
|
+
werewolfDesc: '村民需要找出并投票驱逐隐藏的狼人。',
|
|
30
|
+
avalonName: '抵抗组织:阿瓦隆',
|
|
31
|
+
avalonDesc: '梅林和忠臣在躲避邪恶爪牙的同时完成任务。',
|
|
32
|
+
hitlerName: '秘密希特勒',
|
|
33
|
+
hitlerDesc: '自由派尝试通过法案并找出秘密领袖。',
|
|
34
|
+
customName: '自定义设置',
|
|
35
|
+
customDesc: '为玩家分发带有自定义文本的卡牌。',
|
|
36
|
+
roleWerewolfName: '狼人',
|
|
37
|
+
roleWerewolfDesc: '在夜晚秘密消灭村民。',
|
|
38
|
+
roleWerewolfSecret: '与其他狼人合作,欺骗村民。',
|
|
39
|
+
roleSeerName: '预言家',
|
|
40
|
+
roleSeerDesc: '每晚确认一名玩家的阵营。',
|
|
41
|
+
roleSeerSecret: '找出狼人,但要隐藏自己的身份以求生存。',
|
|
42
|
+
roleDoctorName: '守卫',
|
|
43
|
+
roleDoctorDesc: '每晚保护一名玩家免受消灭。',
|
|
44
|
+
roleDoctorSecret: '确保关键村民的安全。',
|
|
45
|
+
roleVillagerName: '村民',
|
|
46
|
+
roleVillagerDesc: '找出隐藏的狼人。',
|
|
47
|
+
roleVillagerSecret: '进行讨论、分析并投票淘汰嫌疑人。',
|
|
48
|
+
roleMerlinName: '梅林',
|
|
49
|
+
roleMerlinDesc: '知道邪恶阵营的成员是谁。',
|
|
50
|
+
roleMerlinSecret: '引导正义方走向胜利,但不要暴露自己的身份。',
|
|
51
|
+
roleAssassinName: '刺客',
|
|
52
|
+
roleAssassinDesc: '可以在游戏结束时刺杀梅林。',
|
|
53
|
+
roleAssassinSecret: '找出梅林,在最后一刻夺取胜利。',
|
|
54
|
+
roleMinionName: '莫德雷德的爪牙',
|
|
55
|
+
roleMinionDesc: '邪恶阵营的知情成员。',
|
|
56
|
+
roleMinionSecret: '在不被发现的情况下破坏任务。',
|
|
57
|
+
roleServantName: '亚瑟的忠臣',
|
|
58
|
+
roleServantDesc: '为亚瑟王完成任务。',
|
|
59
|
+
roleServantSecret: '讨论、提案并合乎逻辑地投票。',
|
|
60
|
+
roleHitlerName: '希特勒',
|
|
61
|
+
roleHitlerDesc: '法西斯阵营的秘密领袖。',
|
|
62
|
+
roleHitlerSecret: '争取被选为总理,或帮助法西斯通过法案。',
|
|
63
|
+
roleFascistName: '法西斯',
|
|
64
|
+
roleFascistDesc: '协同通过法西斯法案。',
|
|
65
|
+
roleFascistSecret: '保护希特勒并操纵选举结果。',
|
|
66
|
+
roleLiberalName: '自由派',
|
|
67
|
+
roleLiberalDesc: '寻找并阻止法西斯的威胁。',
|
|
68
|
+
roleLiberalSecret: '通过自由法案,或者在可能的情况下消灭希特勒。',
|
|
69
|
+
roleCustomName: '自定义角色',
|
|
70
|
+
roleCustomDesc: '自定义角色的描述。',
|
|
71
|
+
roleCustomSecret: '完成你所在阵营的目标。',
|
|
72
|
+
teamWerewolves: '狼人阵营',
|
|
73
|
+
teamVillagers: '村民阵营',
|
|
74
|
+
teamGood: '正义阵营',
|
|
75
|
+
teamEvil: '邪恶阵营',
|
|
76
|
+
teamFascist: '法西斯阵营',
|
|
77
|
+
teamLiberal: '自由阵营',
|
|
78
|
+
teamCustom: '自定义阵营',
|
|
79
|
+
secretRoleIs: '你的秘密身份是:',
|
|
80
|
+
customRolesTitle: '自定义身份设置',
|
|
81
|
+
customRolePlaceholder: '输入身份名称...',
|
|
82
|
+
neutralLabel: '中立',
|
|
83
|
+
goodLabel: '善良',
|
|
84
|
+
evilLabel: '邪恶',
|
|
85
|
+
customMismatchWarning: '配置的自定义身份数量必须与玩家人数相同。',
|
|
86
|
+
impostorPresetName: '内鬼游戏',
|
|
87
|
+
impostorPresetDesc: '分配内鬼与船员的身份并自定义其数量。',
|
|
88
|
+
impostorTitle: '内鬼设置',
|
|
89
|
+
impostorWarning: '玩家人数必须多于内鬼数量。',
|
|
90
|
+
roleImpostorName: '内鬼',
|
|
91
|
+
roleImpostorDesc: '秘密消灭船员。',
|
|
92
|
+
roleImpostorSecret: '暗中破坏并消灭船员,注意不要被发现。',
|
|
93
|
+
roleCrewmateName: '船员',
|
|
94
|
+
roleCrewmateDesc: '完成任务并找出内鬼。',
|
|
95
|
+
roleCrewmateSecret: '秘密词语是:',
|
|
96
|
+
teamImpostor: '内鬼',
|
|
97
|
+
teamCrewmate: '船员',
|
|
98
|
+
impostorSharedSecretLabel: '共享词语/地点',
|
|
99
|
+
impostorSharedSecretPlaceholder: '例如 地点:餐厅,或者 词语:苹果',
|
|
100
|
+
impostorModeLabel: '内鬼数量决定方式',
|
|
101
|
+
impostorModeFixed: '固定数量',
|
|
102
|
+
impostorModePercentage: '百分比 (%)',
|
|
103
|
+
impostorModeRange: '随机范围',
|
|
104
|
+
impostorFixedLabel: '内鬼数量',
|
|
105
|
+
impostorPercentageLabel: '内鬼百分比 (%)',
|
|
106
|
+
impostorRangeMinLabel: '最小内鬼数',
|
|
107
|
+
impostorRangeMaxLabel: '最大内鬼数',
|
|
108
|
+
impostorWriterLabel: '出题者 / 房东',
|
|
109
|
+
impostorWriterRandom: '系统自动出题',
|
|
110
|
+
roleWriterName: '出题者',
|
|
111
|
+
roleWriterDesc: '你来决定游戏的秘密词语。',
|
|
112
|
+
writerInputPrompt: '你是出题者。请输入本局游戏的秘密词语或地点:',
|
|
113
|
+
writerInputPlaceholder: '例如 餐厅、医院...',
|
|
114
|
+
btnSetWord: '确定并开始',
|
|
115
|
+
impostorWriterPreround: '秘密前置轮(所有玩家)',
|
|
116
|
+
impostorWriterDescHost: '系统出题模式下将使用随机词语。',
|
|
117
|
+
impostorWriterDescPreround: '前置轮模式将由所有玩家分别暗中输入词语,并随机从中选择一个。',
|
|
118
|
+
impostorWriterDescPlayer: '所选玩家负责写下秘密词语,其本人必不会成为内鬼。',
|
|
119
|
+
preroundPassTitle: '前置轮:将设备递给',
|
|
120
|
+
preroundInputPrompt: '暗中写下一个秘密词语或地点(会被隐藏):',
|
|
121
|
+
preroundPassHint: '请不要让其他玩家看到你输入的词语!',
|
|
122
|
+
btnSubmitWord: '提交词语',
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const faq = [
|
|
126
|
+
{
|
|
127
|
+
question: '秘密身份分发器是如何工作的?',
|
|
128
|
+
answer: '秘密身份分发器允许您在一台设备上本地为社交推理解密游戏分配身份。输入玩家名字,选择模板或自定义身份,然后依次传递设备。玩家需要按住确认按钮来查看自己的身份。',
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
const howTo = [
|
|
133
|
+
{
|
|
134
|
+
name: '添加玩家',
|
|
135
|
+
text: '在设置面板中输入参与者的名字。',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: '选择模版',
|
|
139
|
+
text: '选择预设的游戏规则或创建完全自定义的角色池。',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: '传递并确认',
|
|
143
|
+
text: '将设备传递给每位玩家。每位玩家必须长按确认按钮以查看其秘密角色,然后点击下一步。',
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
export const content: ToolLocaleContent<HiddenRoleDealerUI> = {
|
|
148
|
+
slug: 'hidden-role-dealer',
|
|
149
|
+
title: '秘密身份分发器:社交推理与狼人杀卡牌分配器',
|
|
150
|
+
description: '在单台设备上为狼人杀、阿瓦隆、秘密希特勒和内鬼游戏等社交推理桌游分配秘密身份,无需实体卡牌。',
|
|
151
|
+
ui,
|
|
152
|
+
seo: [
|
|
153
|
+
{ type: 'title', text: '桌游身份分发中的阵营平衡策略', level: 2 },
|
|
154
|
+
{ type: 'paragraph', html: '在狼人杀或阿瓦隆中,根据玩家人数合理配置好人与坏人的比例非常关键。本工具提供标准的配比参考以确保游戏平衡。' },
|
|
155
|
+
],
|
|
156
|
+
faq,
|
|
157
|
+
bibliography,
|
|
158
|
+
howTo,
|
|
159
|
+
schemas: [
|
|
160
|
+
{
|
|
161
|
+
'@context': 'https://schema.org',
|
|
162
|
+
'@type': 'FAQPage',
|
|
163
|
+
'mainEntity': faq.map((f) => ({
|
|
164
|
+
'@type': 'Question',
|
|
165
|
+
'name': f.question,
|
|
166
|
+
'acceptedAnswer': {
|
|
167
|
+
'@type': 'Answer',
|
|
168
|
+
'text': f.answer,
|
|
169
|
+
},
|
|
170
|
+
})),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
'@context': 'https://schema.org',
|
|
174
|
+
'@type': 'SoftwareApplication',
|
|
175
|
+
'name': ui.title,
|
|
176
|
+
'operatingSystem': 'All',
|
|
177
|
+
'applicationCategory': 'UtilitiesApplication',
|
|
178
|
+
'browserRequirements': 'Requires HTML5. Requires JavaScript.',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
'@context': 'https://schema.org',
|
|
182
|
+
'@type': 'HowTo',
|
|
183
|
+
'name': ui.setupTitle,
|
|
184
|
+
'step': howTo.map((h) => ({
|
|
185
|
+
'@type': 'HowToStep',
|
|
186
|
+
'name': h.name,
|
|
187
|
+
'text': h.text,
|
|
188
|
+
})),
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../../types';
|
|
2
|
+
import { hiddenRoleDealer } from './entry';
|
|
3
|
+
export * from './entry';
|
|
4
|
+
|
|
5
|
+
export const HIDDEN_ROLE_DEALER_TOOL: ToolDefinition = {
|
|
6
|
+
entry: hiddenRoleDealer,
|
|
7
|
+
Component: () => import('./component.astro'),
|
|
8
|
+
SEOComponent: () => import('./seo.astro'),
|
|
9
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
10
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { assignRoles, addPlayer, removePlayer, updatePlayerName, addCustomRole, removeCustomRole, calculateImpostorCount, generateImpostorRoles } from './logic';
|
|
3
|
+
import type { Player, Role } from './types';
|
|
4
|
+
|
|
5
|
+
describe('Hidden Role Dealer Logic', () => {
|
|
6
|
+
const mockRoles: Role[] = [
|
|
7
|
+
{ id: '1', name: 'Role A', description: 'Desc A', team: 'Team A', secretInfo: 'Info A', alignment: 'good' },
|
|
8
|
+
{ id: '2', name: 'Role B', description: 'Desc B', team: 'Team B', secretInfo: 'Info B', alignment: 'evil' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
it('should add a player', () => {
|
|
12
|
+
const players: Player[] = [];
|
|
13
|
+
const result = addPlayer(players, 'Player 1');
|
|
14
|
+
expect(result.length).toBe(1);
|
|
15
|
+
expect(result[0].name).toBe('Player 1');
|
|
16
|
+
expect(result[0].role).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should not add an empty player name', () => {
|
|
20
|
+
const players: Player[] = [];
|
|
21
|
+
const result = addPlayer(players, ' ');
|
|
22
|
+
expect(result.length).toBe(0);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should remove a player', () => {
|
|
26
|
+
const players: Player[] = [
|
|
27
|
+
{ id: 'p1', name: 'Player 1', role: null, revealed: false },
|
|
28
|
+
{ id: 'p2', name: 'Player 2', role: null, revealed: false },
|
|
29
|
+
];
|
|
30
|
+
const result = removePlayer(players, 'p1');
|
|
31
|
+
expect(result.length).toBe(1);
|
|
32
|
+
expect(result[0].id).toBe('p2');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should update player name', () => {
|
|
36
|
+
const players: Player[] = [
|
|
37
|
+
{ id: 'p1', name: 'Player 1', role: null, revealed: false },
|
|
38
|
+
];
|
|
39
|
+
const result = updatePlayerName(players, 'p1', 'New Name');
|
|
40
|
+
expect(result[0].name).toBe('New Name');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should assign roles to players', () => {
|
|
44
|
+
const players: Player[] = [
|
|
45
|
+
{ id: 'p1', name: 'Player 1', role: null, revealed: false },
|
|
46
|
+
{ id: 'p2', name: 'Player 2', role: null, revealed: false },
|
|
47
|
+
];
|
|
48
|
+
const result = assignRoles(players, mockRoles);
|
|
49
|
+
expect(result[0].role).not.toBeNull();
|
|
50
|
+
expect(result[1].role).not.toBeNull();
|
|
51
|
+
expect(result[0].role!.id).not.toBe(result[1].role!.id);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should add custom roles', () => {
|
|
55
|
+
const roles: Role[] = [];
|
|
56
|
+
const result = addCustomRole(roles, 'Detective', 'good');
|
|
57
|
+
expect(result.length).toBe(1);
|
|
58
|
+
expect(result[0].name).toBe('Detective');
|
|
59
|
+
expect(result[0].alignment).toBe('good');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should remove custom roles', () => {
|
|
63
|
+
const roles: Role[] = [
|
|
64
|
+
{ id: 'r1', name: 'Detective', description: '', team: '', secretInfo: '', alignment: 'good' }
|
|
65
|
+
];
|
|
66
|
+
const result = removeCustomRole(roles, 'r1');
|
|
67
|
+
expect(result.length).toBe(0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should calculate impostor count correctly', () => {
|
|
71
|
+
const vals = { fixedVal: 2, percentVal: 25, minVal: 1, maxVal: 3 };
|
|
72
|
+
expect(calculateImpostorCount('fixed', 8, vals)).toBe(2);
|
|
73
|
+
expect(calculateImpostorCount('percentage', 8, { ...vals, fixedVal: 1 })).toBe(2);
|
|
74
|
+
const rangeVal = calculateImpostorCount('range', 8, { ...vals, fixedVal: 1 });
|
|
75
|
+
expect(rangeVal).toBeGreaterThanOrEqual(1);
|
|
76
|
+
expect(rangeVal).toBeLessThanOrEqual(3);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should generate impostor roles correctly with shared secret', () => {
|
|
80
|
+
const ui = {
|
|
81
|
+
roleImpostorName: 'Impostor',
|
|
82
|
+
roleImpostorDesc: 'Secretly eliminate Crewmates.',
|
|
83
|
+
teamImpostor: 'Impostor',
|
|
84
|
+
roleImpostorSecret: 'Sabotage.',
|
|
85
|
+
roleCrewmateName: 'Crewmate',
|
|
86
|
+
roleCrewmateDesc: 'Find the Impostor.',
|
|
87
|
+
teamCrewmate: 'Crewmate',
|
|
88
|
+
roleCrewmateSecret: 'Secret word is:',
|
|
89
|
+
};
|
|
90
|
+
const roles = generateImpostorRoles(6, 2, 'Restaurant', ui);
|
|
91
|
+
expect(roles.length).toBe(6);
|
|
92
|
+
const impostors = roles.filter(r => r.alignment === 'evil');
|
|
93
|
+
const crewmates = roles.filter(r => r.alignment === 'good');
|
|
94
|
+
expect(impostors.length).toBe(2);
|
|
95
|
+
expect(crewmates.length).toBe(4);
|
|
96
|
+
expect(crewmates[0].secretInfo).toBe('Secret word is: Restaurant');
|
|
97
|
+
expect(impostors[0].secretInfo).toBe('Sabotage.');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { Player, Role, ImpostorValues } from './types';
|
|
2
|
+
|
|
3
|
+
export function shuffleArray<T>(array: T[]): T[] {
|
|
4
|
+
const result = [...array];
|
|
5
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
6
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
7
|
+
const temp = result[i];
|
|
8
|
+
result[i] = result[j];
|
|
9
|
+
result[j] = temp;
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function assignRoles(players: Player[], roles: Role[]): Player[] {
|
|
15
|
+
const shuffledRoles = shuffleArray(roles);
|
|
16
|
+
return players.map((player, index) => ({
|
|
17
|
+
...player,
|
|
18
|
+
role: shuffledRoles[index] || null,
|
|
19
|
+
revealed: false,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function addPlayer(players: Player[], name: string): Player[] {
|
|
24
|
+
const cleanedName = name.trim();
|
|
25
|
+
if (!cleanedName) {
|
|
26
|
+
return players;
|
|
27
|
+
}
|
|
28
|
+
return [
|
|
29
|
+
...players,
|
|
30
|
+
{
|
|
31
|
+
id: `player-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
32
|
+
name: cleanedName,
|
|
33
|
+
role: null,
|
|
34
|
+
revealed: false,
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function removePlayer(players: Player[], id: string): Player[] {
|
|
40
|
+
return players.filter((p) => p.id !== id);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function updatePlayerName(players: Player[], id: string, name: string): Player[] {
|
|
44
|
+
return players.map((p) => (p.id === id ? { ...p, name: name.trim() } : p));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function addCustomRole(roles: Role[], name: string, alignment: 'good' | 'evil' | 'neutral'): Role[] {
|
|
48
|
+
const cleaned = name.trim();
|
|
49
|
+
if (!cleaned) return roles;
|
|
50
|
+
return [
|
|
51
|
+
...roles,
|
|
52
|
+
{
|
|
53
|
+
id: `r-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
54
|
+
name: cleaned,
|
|
55
|
+
description: 'Custom game role.',
|
|
56
|
+
team: alignment.toUpperCase(),
|
|
57
|
+
secretInfo: 'Achieve your victory conditions.',
|
|
58
|
+
alignment,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function removeCustomRole(roles: Role[], id: string): Role[] {
|
|
64
|
+
return roles.filter((r) => r.id !== id);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function calculateImpostorCount(
|
|
68
|
+
mode: string,
|
|
69
|
+
totalPlayers: number,
|
|
70
|
+
vals: ImpostorValues
|
|
71
|
+
): number {
|
|
72
|
+
if (mode === 'percentage') {
|
|
73
|
+
return Math.max(1, Math.round((vals.percentVal / 100) * totalPlayers));
|
|
74
|
+
}
|
|
75
|
+
if (mode === 'range') {
|
|
76
|
+
const min = Math.max(1, vals.minVal);
|
|
77
|
+
const max = Math.max(min, vals.maxVal);
|
|
78
|
+
const actualMax = Math.min(max, totalPlayers - 1);
|
|
79
|
+
const actualMin = Math.min(min, actualMax);
|
|
80
|
+
return Math.floor(Math.random() * (actualMax - actualMin + 1)) + actualMin;
|
|
81
|
+
}
|
|
82
|
+
return Math.max(1, vals.fixedVal);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function createImpostorRole(index: number, ui: Record<string, string>): Role {
|
|
86
|
+
return {
|
|
87
|
+
id: `imp-${index}`,
|
|
88
|
+
name: ui.roleImpostorName || 'Impostor',
|
|
89
|
+
description: ui.roleImpostorDesc || 'Secretly eliminate Crewmates.',
|
|
90
|
+
team: ui.teamImpostor || 'Impostor',
|
|
91
|
+
secretInfo: ui.roleImpostorSecret || 'Sabotage and eliminate without getting caught.',
|
|
92
|
+
alignment: 'evil',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createCrewmateRole(index: number, secretMsg: string, ui: Record<string, string>): Role {
|
|
97
|
+
return {
|
|
98
|
+
id: `crew-${index}`,
|
|
99
|
+
name: ui.roleCrewmateName || 'Crewmate',
|
|
100
|
+
description: ui.roleCrewmateDesc || 'Complete tasks and find the Impostor.',
|
|
101
|
+
team: ui.teamCrewmate || 'Crewmate',
|
|
102
|
+
secretInfo: secretMsg,
|
|
103
|
+
alignment: 'good',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function generateImpostorRoles(
|
|
108
|
+
totalPlayers: number,
|
|
109
|
+
impostorCount: number,
|
|
110
|
+
sharedSecret: string,
|
|
111
|
+
ui: Record<string, string>
|
|
112
|
+
): Role[] {
|
|
113
|
+
const roles: Role[] = [];
|
|
114
|
+
const actualImpostors = Math.min(impostorCount, totalPlayers - 1);
|
|
115
|
+
for (let i = 0; i < actualImpostors; i++) {
|
|
116
|
+
roles.push(createImpostorRole(i, ui));
|
|
117
|
+
}
|
|
118
|
+
const prefix = ui.roleCrewmateSecret || 'The Secret Word is:';
|
|
119
|
+
const secretMsg = `${prefix} ${sharedSecret}`;
|
|
120
|
+
while (roles.length < totalPlayers) {
|
|
121
|
+
roles.push(createCrewmateRole(roles.length, secretMsg, ui));
|
|
122
|
+
}
|
|
123
|
+
return roles;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const RANDOM_WORDS = [
|
|
127
|
+
'Restaurant', 'Space Station', 'Submarine', 'Supermarket', 'Movie Theater',
|
|
128
|
+
'School', 'Hospital', 'Pirate Ship', 'Amusement Park', 'Hotel',
|
|
129
|
+
'Airport', 'Police Station', 'Museum', 'Bank', 'Casino'
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
export function getRandomSecretWord(): string {
|
|
133
|
+
return RANDOM_WORDS[Math.floor(Math.random() * RANDOM_WORDS.length)];
|
|
134
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { addCustomRole, removeCustomRole } from '../logic';
|
|
2
|
+
import { loadSavedCustomRoles } from './storage';
|
|
3
|
+
import type { Role } from '../types';
|
|
4
|
+
|
|
5
|
+
export class CustomRoleManager {
|
|
6
|
+
private roles: Role[] = [];
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
this.roles = loadSavedCustomRoles();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public getRoles(): Role[] {
|
|
13
|
+
return this.roles;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public add(name: string, alignment: 'good' | 'evil' | 'neutral'): void {
|
|
17
|
+
this.roles = addCustomRole(this.roles, name, alignment);
|
|
18
|
+
localStorage.setItem('hidden-role-dealer-custom-roles', JSON.stringify(this.roles));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public addFromInput(container: HTMLElement): void {
|
|
22
|
+
const input = container.querySelector('#input-role-name') as HTMLInputElement;
|
|
23
|
+
const select = container.querySelector('#select-role-alignment') as HTMLSelectElement;
|
|
24
|
+
if (input && select && input.value.trim()) {
|
|
25
|
+
this.add(input.value.trim(), select.value as 'good' | 'evil' | 'neutral');
|
|
26
|
+
input.value = '';
|
|
27
|
+
input.focus();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public delete(id: string): void {
|
|
32
|
+
this.roles = removeCustomRole(this.roles, id);
|
|
33
|
+
localStorage.setItem('hidden-role-dealer-custom-roles', JSON.stringify(this.roles));
|
|
34
|
+
}
|
|
35
|
+
}
|