feat: localize all UI text to Korean
Translate all user-facing strings to Korean across 25 files: - UI screens: title, nickname, lobby, class select, waiting, game, shop, result, help, leaderboard, achievements, codex, stats - Game logic: combat logs, events, achievements, mutations, emotes, lobby errors, session messages - Keep English for: class names, monster names, item names, relic names Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ var comboDefs = []ComboDef{
|
|||||||
{Class: entity.ClassMage, ActionType: "skill"},
|
{Class: entity.ClassMage, ActionType: "skill"},
|
||||||
{Class: entity.ClassWarrior, ActionType: "attack"},
|
{Class: entity.ClassWarrior, ActionType: "attack"},
|
||||||
},
|
},
|
||||||
Effect: ComboEffect{DamageMultiplier: 1.5, Message: "💥 ICE SHATTER! Frozen enemies shatter!"},
|
Effect: ComboEffect{DamageMultiplier: 1.5, Message: "💥 ICE SHATTER! 동결된 적이 산산조각!"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Holy Assault",
|
Name: "Holy Assault",
|
||||||
@@ -35,7 +35,7 @@ var comboDefs = []ComboDef{
|
|||||||
{Class: entity.ClassHealer, ActionType: "skill"},
|
{Class: entity.ClassHealer, ActionType: "skill"},
|
||||||
{Class: entity.ClassWarrior, ActionType: "attack"},
|
{Class: entity.ClassWarrior, ActionType: "attack"},
|
||||||
},
|
},
|
||||||
Effect: ComboEffect{DamageMultiplier: 1.3, HealAll: 10, Message: "✨ HOLY ASSAULT! Blessed strikes heal the party!"},
|
Effect: ComboEffect{DamageMultiplier: 1.3, HealAll: 10, Message: "✨ HOLY ASSAULT! 축복받은 공격이 파티를 치유!"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Shadow Strike",
|
Name: "Shadow Strike",
|
||||||
@@ -43,7 +43,7 @@ var comboDefs = []ComboDef{
|
|||||||
{Class: entity.ClassRogue, ActionType: "skill"},
|
{Class: entity.ClassRogue, ActionType: "skill"},
|
||||||
{Class: entity.ClassMage, ActionType: "attack"},
|
{Class: entity.ClassMage, ActionType: "attack"},
|
||||||
},
|
},
|
||||||
Effect: ComboEffect{DamageMultiplier: 1.4, Message: "🗡️ SHADOW STRIKE! Magical shadows amplify the attack!"},
|
Effect: ComboEffect{DamageMultiplier: 1.4, Message: "🗡️ SHADOW STRIKE! 마법의 그림자가 공격을 증폭!"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Full Assault",
|
Name: "Full Assault",
|
||||||
@@ -52,7 +52,7 @@ var comboDefs = []ComboDef{
|
|||||||
{Class: entity.ClassMage, ActionType: "attack"},
|
{Class: entity.ClassMage, ActionType: "attack"},
|
||||||
{Class: entity.ClassRogue, ActionType: "attack"},
|
{Class: entity.ClassRogue, ActionType: "attack"},
|
||||||
},
|
},
|
||||||
Effect: ComboEffect{DamageMultiplier: 1.3, BonusDamage: 5, Message: "⚔️ FULL ASSAULT! Combined attack overwhelms!"},
|
Effect: ComboEffect{DamageMultiplier: 1.3, BonusDamage: 5, Message: "⚔️ FULL ASSAULT! 합동 공격으로 압도!"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Restoration",
|
Name: "Restoration",
|
||||||
@@ -60,7 +60,7 @@ var comboDefs = []ComboDef{
|
|||||||
{Class: entity.ClassHealer, ActionType: "skill"},
|
{Class: entity.ClassHealer, ActionType: "skill"},
|
||||||
{Class: entity.ClassRogue, ActionType: "item"},
|
{Class: entity.ClassRogue, ActionType: "item"},
|
||||||
},
|
},
|
||||||
Effect: ComboEffect{HealAll: 20, Message: "💚 RESTORATION! Combined healing surges!"},
|
Effect: ComboEffect{HealAll: 20, Message: "💚 RESTORATION! 합동 치유가 폭발적으로 발동!"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (r RoomType) String() string {
|
func (r RoomType) String() string {
|
||||||
return [...]string{"Combat", "Treasure", "Shop", "Event", "Empty", "Boss", "Secret", "MiniBoss"}[r]
|
return [...]string{"전투", "보물", "상점", "이벤트", "빈 방", "보스", "비밀", "미니보스"}[r]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tile int
|
type Tile int
|
||||||
|
|||||||
@@ -179,22 +179,22 @@ func (p *Player) TickEffects() []string {
|
|||||||
if p.HP <= 0 {
|
if p.HP <= 0 {
|
||||||
p.HP = 1 // Poison can't kill, leaves at 1 HP
|
p.HP = 1 // Poison can't kill, leaves at 1 HP
|
||||||
}
|
}
|
||||||
msgs = append(msgs, fmt.Sprintf("%s takes %d poison damage", p.Name, e.Value))
|
msgs = append(msgs, fmt.Sprintf("%s 독 피해 %d", p.Name, e.Value))
|
||||||
case StatusBurn:
|
case StatusBurn:
|
||||||
p.HP -= e.Value
|
p.HP -= e.Value
|
||||||
if p.HP <= 0 {
|
if p.HP <= 0 {
|
||||||
p.HP = 0
|
p.HP = 0
|
||||||
p.Dead = true
|
p.Dead = true
|
||||||
}
|
}
|
||||||
msgs = append(msgs, fmt.Sprintf("%s takes %d burn damage", p.Name, e.Value))
|
msgs = append(msgs, fmt.Sprintf("%s 화상 피해 %d", p.Name, e.Value))
|
||||||
case StatusFreeze:
|
case StatusFreeze:
|
||||||
msgs = append(msgs, fmt.Sprintf("%s is frozen!", p.Name))
|
msgs = append(msgs, fmt.Sprintf("%s 동결됨!", p.Name))
|
||||||
case StatusBleed:
|
case StatusBleed:
|
||||||
p.HP -= e.Value
|
p.HP -= e.Value
|
||||||
msgs = append(msgs, fmt.Sprintf("%s takes %d bleed damage", p.Name, e.Value))
|
msgs = append(msgs, fmt.Sprintf("%s 출혈 피해 %d", p.Name, e.Value))
|
||||||
e.Value++ // Bleed intensifies each turn
|
e.Value++ // Bleed intensifies each turn
|
||||||
case StatusCurse:
|
case StatusCurse:
|
||||||
msgs = append(msgs, fmt.Sprintf("%s is cursed! Healing reduced", p.Name))
|
msgs = append(msgs, fmt.Sprintf("%s 저주 상태! 회복량 감소", p.Name))
|
||||||
}
|
}
|
||||||
if p.HP < 0 {
|
if p.HP < 0 {
|
||||||
p.HP = 0
|
p.HP = 0
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ func TestBleedEffect(t *testing.T) {
|
|||||||
p.AddEffect(ActiveEffect{Type: StatusBleed, Duration: 3, Value: 2})
|
p.AddEffect(ActiveEffect{Type: StatusBleed, Duration: 3, Value: 2})
|
||||||
|
|
||||||
msgs := p.TickEffects()
|
msgs := p.TickEffects()
|
||||||
if len(msgs) == 0 || !strings.Contains(msgs[0], "bleed") {
|
if len(msgs) == 0 || !strings.Contains(msgs[0], "출혈") {
|
||||||
t.Error("expected bleed damage message")
|
t.Error("expected bleed damage message")
|
||||||
}
|
}
|
||||||
if p.HP != startHP-2 {
|
if p.HP != startHP-2 {
|
||||||
@@ -231,7 +231,7 @@ func TestFreezeTickMessage(t *testing.T) {
|
|||||||
p := NewPlayer("Test", ClassMage)
|
p := NewPlayer("Test", ClassMage)
|
||||||
p.AddEffect(ActiveEffect{Type: StatusFreeze, Duration: 1, Value: 0})
|
p.AddEffect(ActiveEffect{Type: StatusFreeze, Duration: 1, Value: 0})
|
||||||
msgs := p.TickEffects()
|
msgs := p.TickEffects()
|
||||||
if len(msgs) == 0 || !strings.Contains(msgs[0], "frozen") {
|
if len(msgs) == 0 || !strings.Contains(msgs[0], "동결") {
|
||||||
t.Error("expected freeze message")
|
t.Error("expected freeze message")
|
||||||
}
|
}
|
||||||
// Freeze duration 1 -> removed after tick
|
// Freeze duration 1 -> removed after tick
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
var emotes = map[string]string{
|
var emotes = map[string]string{
|
||||||
"/hi": "👋 waves hello!",
|
"/hi": "👋 인사합니다!",
|
||||||
"/gg": "🎉 says GG!",
|
"/gg": "🎉 GG!",
|
||||||
"/go": "⚔️ says Let's go!",
|
"/go": "⚔️ 가자!",
|
||||||
"/wait": "✋ says Wait!",
|
"/wait": "✋ 기다려!",
|
||||||
"/help": "🆘 calls for help!",
|
"/help": "🆘 도움 요청!",
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseEmote(input string) (string, bool) {
|
func ParseEmote(input string) (string, bool) {
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ func TestParseEmote(t *testing.T) {
|
|||||||
isEmote bool
|
isEmote bool
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"/hi", true, "👋 waves hello!"},
|
{"/hi", true, "👋 인사합니다!"},
|
||||||
{"/gg", true, "🎉 says GG!"},
|
{"/gg", true, "🎉 GG!"},
|
||||||
{"/go", true, "⚔️ says Let's go!"},
|
{"/go", true, "⚔️ 가자!"},
|
||||||
{"/wait", true, "✋ says Wait!"},
|
{"/wait", true, "✋ 기다려!"},
|
||||||
{"/help", true, "🆘 calls for help!"},
|
{"/help", true, "🆘 도움 요청!"},
|
||||||
{"/unknown", false, ""},
|
{"/unknown", false, ""},
|
||||||
{"hello", false, ""},
|
{"hello", false, ""},
|
||||||
{"", false, ""},
|
{"", false, ""},
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (s *GameSession) EnterRoom(roomIdx int) {
|
|||||||
s.signalCombat()
|
s.signalCombat()
|
||||||
case dungeon.RoomShop:
|
case dungeon.RoomShop:
|
||||||
if s.hasMutation("no_shop") {
|
if s.hasMutation("no_shop") {
|
||||||
s.addLog("The shop is closed! (Weekly mutation)")
|
s.addLog("상점이 닫혔습니다! (주간 변이)")
|
||||||
room.Cleared = true
|
room.Cleared = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ func (s *GameSession) grantTreasure() {
|
|||||||
floor := s.state.FloorNum
|
floor := s.state.FloorNum
|
||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
if len(p.Inventory) >= s.cfg.Game.InventoryLimit {
|
if len(p.Inventory) >= s.cfg.Game.InventoryLimit {
|
||||||
s.addLog(fmt.Sprintf("%s's inventory is full!", p.Name))
|
s.addLog(fmt.Sprintf("%s의 인벤토리가 가득 찼습니다!", p.Name))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if rand.Float64() < 0.5 {
|
if rand.Float64() < 0.5 {
|
||||||
@@ -178,14 +178,14 @@ func (s *GameSession) grantTreasure() {
|
|||||||
Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: bonus,
|
Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: bonus,
|
||||||
}
|
}
|
||||||
p.Inventory = append(p.Inventory, item)
|
p.Inventory = append(p.Inventory, item)
|
||||||
s.addLog(fmt.Sprintf("%s found %s (ATK+%d)", p.Name, item.Name, item.Bonus))
|
s.addLog(fmt.Sprintf("%s %s 발견 (ATK+%d)", p.Name, item.Name, item.Bonus))
|
||||||
} else {
|
} else {
|
||||||
bonus := 2 + rand.Intn(4) + floor/4
|
bonus := 2 + rand.Intn(4) + floor/4
|
||||||
item := entity.Item{
|
item := entity.Item{
|
||||||
Name: armorName(floor), Type: entity.ItemArmor, Bonus: bonus,
|
Name: armorName(floor), Type: entity.ItemArmor, Bonus: bonus,
|
||||||
}
|
}
|
||||||
p.Inventory = append(p.Inventory, item)
|
p.Inventory = append(p.Inventory, item)
|
||||||
s.addLog(fmt.Sprintf("%s found %s (DEF+%d)", p.Name, item.Name, item.Bonus))
|
s.addLog(fmt.Sprintf("%s %s 발견 (DEF+%d)", p.Name, item.Name, item.Bonus))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,7 +245,7 @@ func armorName(floor int) string {
|
|||||||
func (s *GameSession) triggerEvent() {
|
func (s *GameSession) triggerEvent() {
|
||||||
event := PickRandomEvent()
|
event := PickRandomEvent()
|
||||||
s.state.LastEventName = event.Name
|
s.state.LastEventName = event.Name
|
||||||
s.addLog(fmt.Sprintf("Event: %s — %s", event.Name, event.Description))
|
s.addLog(fmt.Sprintf("이벤트: %s — %s", event.Name, event.Description))
|
||||||
|
|
||||||
// Auto-resolve with a random choice
|
// Auto-resolve with a random choice
|
||||||
choice := event.Choices[rand.Intn(len(event.Choices))]
|
choice := event.Choices[rand.Intn(len(event.Choices))]
|
||||||
@@ -267,10 +267,10 @@ func (s *GameSession) triggerEvent() {
|
|||||||
if outcome.HPChange > 0 {
|
if outcome.HPChange > 0 {
|
||||||
before := target.HP
|
before := target.HP
|
||||||
target.Heal(outcome.HPChange)
|
target.Heal(outcome.HPChange)
|
||||||
s.addLog(fmt.Sprintf(" %s heals %d HP", target.Name, target.HP-before))
|
s.addLog(fmt.Sprintf(" %s HP %d 회복", target.Name, target.HP-before))
|
||||||
} else if outcome.HPChange < 0 {
|
} else if outcome.HPChange < 0 {
|
||||||
target.TakeDamage(-outcome.HPChange)
|
target.TakeDamage(-outcome.HPChange)
|
||||||
s.addLog(fmt.Sprintf(" %s takes %d damage", target.Name, -outcome.HPChange))
|
s.addLog(fmt.Sprintf(" %s %d 피해를 받음", target.Name, -outcome.HPChange))
|
||||||
}
|
}
|
||||||
|
|
||||||
if outcome.GoldChange != 0 {
|
if outcome.GoldChange != 0 {
|
||||||
@@ -279,9 +279,9 @@ func (s *GameSession) triggerEvent() {
|
|||||||
target.Gold = 0
|
target.Gold = 0
|
||||||
}
|
}
|
||||||
if outcome.GoldChange > 0 {
|
if outcome.GoldChange > 0 {
|
||||||
s.addLog(fmt.Sprintf(" %s gains %d gold", target.Name, outcome.GoldChange))
|
s.addLog(fmt.Sprintf(" %s 골드 %d 획득", target.Name, outcome.GoldChange))
|
||||||
} else {
|
} else {
|
||||||
s.addLog(fmt.Sprintf(" %s loses %d gold", target.Name, -outcome.GoldChange))
|
s.addLog(fmt.Sprintf(" %s 골드 %d 잃음", target.Name, -outcome.GoldChange))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,27 +292,27 @@ func (s *GameSession) triggerEvent() {
|
|||||||
bonus := 3 + rand.Intn(6) + floor/3
|
bonus := 3 + rand.Intn(6) + floor/3
|
||||||
item := entity.Item{Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: bonus}
|
item := entity.Item{Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: bonus}
|
||||||
target.Inventory = append(target.Inventory, item)
|
target.Inventory = append(target.Inventory, item)
|
||||||
s.addLog(fmt.Sprintf(" %s found %s (ATK+%d)", target.Name, item.Name, item.Bonus))
|
s.addLog(fmt.Sprintf(" %s %s 발견 (ATK+%d)", target.Name, item.Name, item.Bonus))
|
||||||
} else {
|
} else {
|
||||||
bonus := 2 + rand.Intn(4) + floor/4
|
bonus := 2 + rand.Intn(4) + floor/4
|
||||||
item := entity.Item{Name: armorName(floor), Type: entity.ItemArmor, Bonus: bonus}
|
item := entity.Item{Name: armorName(floor), Type: entity.ItemArmor, Bonus: bonus}
|
||||||
target.Inventory = append(target.Inventory, item)
|
target.Inventory = append(target.Inventory, item)
|
||||||
s.addLog(fmt.Sprintf(" %s found %s (DEF+%d)", target.Name, item.Name, item.Bonus))
|
s.addLog(fmt.Sprintf(" %s %s 발견 (DEF+%d)", target.Name, item.Name, item.Bonus))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.addLog(fmt.Sprintf(" %s's inventory is full!", target.Name))
|
s.addLog(fmt.Sprintf(" %s의 인벤토리가 가득 찼습니다!", target.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GameSession) grantSecretTreasure() {
|
func (s *GameSession) grantSecretTreasure() {
|
||||||
s.addLog("You discovered a secret room filled with treasure!")
|
s.addLog("보물로 가득 찬 비밀의 방을 발견했습니다!")
|
||||||
floor := s.state.FloorNum
|
floor := s.state.FloorNum
|
||||||
// Double treasure: grant two items per player
|
// Double treasure: grant two items per player
|
||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
if len(p.Inventory) >= s.cfg.Game.InventoryLimit {
|
if len(p.Inventory) >= s.cfg.Game.InventoryLimit {
|
||||||
s.addLog(fmt.Sprintf("%s's inventory is full!", p.Name))
|
s.addLog(fmt.Sprintf("%s의 인벤토리가 가득 찼습니다!", p.Name))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if rand.Float64() < 0.5 {
|
if rand.Float64() < 0.5 {
|
||||||
@@ -321,14 +321,14 @@ func (s *GameSession) grantSecretTreasure() {
|
|||||||
Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: bonus,
|
Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: bonus,
|
||||||
}
|
}
|
||||||
p.Inventory = append(p.Inventory, item)
|
p.Inventory = append(p.Inventory, item)
|
||||||
s.addLog(fmt.Sprintf("%s found %s (ATK+%d)", p.Name, item.Name, item.Bonus))
|
s.addLog(fmt.Sprintf("%s %s 발견 (ATK+%d)", p.Name, item.Name, item.Bonus))
|
||||||
} else {
|
} else {
|
||||||
bonus := 2 + rand.Intn(4) + floor/4
|
bonus := 2 + rand.Intn(4) + floor/4
|
||||||
item := entity.Item{
|
item := entity.Item{
|
||||||
Name: armorName(floor), Type: entity.ItemArmor, Bonus: bonus,
|
Name: armorName(floor), Type: entity.ItemArmor, Bonus: bonus,
|
||||||
}
|
}
|
||||||
p.Inventory = append(p.Inventory, item)
|
p.Inventory = append(p.Inventory, item)
|
||||||
s.addLog(fmt.Sprintf("%s found %s (DEF+%d)", p.Name, item.Name, item.Bonus))
|
s.addLog(fmt.Sprintf("%s %s 발견 (DEF+%d)", p.Name, item.Name, item.Bonus))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,7 +376,7 @@ func (s *GameSession) spawnMiniBoss() {
|
|||||||
miniBoss.ATK = int(float64(miniBoss.ATK) * mult)
|
miniBoss.ATK = int(float64(miniBoss.ATK) * mult)
|
||||||
}
|
}
|
||||||
s.state.Monsters = []*entity.Monster{miniBoss}
|
s.state.Monsters = []*entity.Monster{miniBoss}
|
||||||
s.addLog(fmt.Sprintf("A mini-boss appears: %s!", miniBoss.Name))
|
s.addLog(fmt.Sprintf("미니보스 등장: %s!", miniBoss.Name))
|
||||||
|
|
||||||
// Reset skill uses for all players at combat start
|
// Reset skill uses for all players at combat start
|
||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
|
|||||||
@@ -117,10 +117,10 @@ func (l *Lobby) InvitePlayer(roomCode, fingerprint string) error {
|
|||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
p, ok := l.online[fingerprint]
|
p, ok := l.online[fingerprint]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("player not online")
|
return fmt.Errorf("플레이어가 온라인이 아닙니다")
|
||||||
}
|
}
|
||||||
if p.InRoom != "" {
|
if p.InRoom != "" {
|
||||||
return fmt.Errorf("player already in a room")
|
return fmt.Errorf("플레이어가 이미 방에 있습니다")
|
||||||
}
|
}
|
||||||
// Store the invite as a pending field
|
// Store the invite as a pending field
|
||||||
p.InRoom = "invited:" + roomCode
|
p.InRoom = "invited:" + roomCode
|
||||||
@@ -148,13 +148,13 @@ func (l *Lobby) JoinRoom(code, playerName, fingerprint string) error {
|
|||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
room, ok := l.rooms[code]
|
room, ok := l.rooms[code]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("room %s not found", code)
|
return fmt.Errorf("방 %s을(를) 찾을 수 없습니다", code)
|
||||||
}
|
}
|
||||||
if len(room.Players) >= l.cfg.Game.MaxPlayers {
|
if len(room.Players) >= l.cfg.Game.MaxPlayers {
|
||||||
return fmt.Errorf("room %s is full", code)
|
return fmt.Errorf("방 %s이(가) 가득 찼습니다", code)
|
||||||
}
|
}
|
||||||
if room.Status != RoomWaiting {
|
if room.Status != RoomWaiting {
|
||||||
return fmt.Errorf("room %s already in progress", code)
|
return fmt.Errorf("방 %s이(가) 이미 진행 중입니다", code)
|
||||||
}
|
}
|
||||||
room.Players = append(room.Players, LobbyPlayer{Name: playerName, Fingerprint: fingerprint})
|
room.Players = append(room.Players, LobbyPlayer{Name: playerName, Fingerprint: fingerprint})
|
||||||
slog.Info("player joined", "room", code, "player", playerName)
|
slog.Info("player joined", "room", code, "player", playerName)
|
||||||
|
|||||||
@@ -19,15 +19,15 @@ type Mutation struct {
|
|||||||
|
|
||||||
// Mutations is the list of all available mutations.
|
// Mutations is the list of all available mutations.
|
||||||
var Mutations = []Mutation{
|
var Mutations = []Mutation{
|
||||||
{ID: "no_skills", Name: "Skill Lockout", Description: "Class skills are disabled",
|
{ID: "no_skills", Name: "스킬 봉인", Description: "직업 스킬 사용 불가",
|
||||||
Apply: func(cfg *config.GameConfig) { cfg.SkillUses = 0 }},
|
Apply: func(cfg *config.GameConfig) { cfg.SkillUses = 0 }},
|
||||||
{ID: "speed_run", Name: "Speed Run", Description: "Turn timeout halved",
|
{ID: "speed_run", Name: "스피드 런", Description: "턴 제한 시간 절반",
|
||||||
Apply: func(cfg *config.GameConfig) { cfg.TurnTimeoutSec = max(cfg.TurnTimeoutSec/2, 2) }},
|
Apply: func(cfg *config.GameConfig) { cfg.TurnTimeoutSec = max(cfg.TurnTimeoutSec/2, 2) }},
|
||||||
{ID: "no_shop", Name: "Shop Closed", Description: "Shops are unavailable",
|
{ID: "no_shop", Name: "상점 폐쇄", Description: "상점 이용 불가",
|
||||||
Apply: func(cfg *config.GameConfig) {}}, // handled at runtime in EnterRoom
|
Apply: func(cfg *config.GameConfig) {}}, // handled at runtime in EnterRoom
|
||||||
{ID: "glass_cannon", Name: "Glass Cannon", Description: "Double damage, half HP",
|
{ID: "glass_cannon", Name: "유리 대포", Description: "피해 2배, HP 절반",
|
||||||
Apply: func(cfg *config.GameConfig) {}}, // handled at runtime in AddPlayer/spawnMonsters
|
Apply: func(cfg *config.GameConfig) {}}, // handled at runtime in AddPlayer/spawnMonsters
|
||||||
{ID: "elite_flood", Name: "Elite Flood", Description: "All monsters are elite",
|
{ID: "elite_flood", Name: "엘리트 범람", Description: "모든 몬스터가 엘리트",
|
||||||
Apply: func(cfg *config.GameConfig) {}}, // handled at runtime in spawnMonsters
|
Apply: func(cfg *config.GameConfig) {}}, // handled at runtime in spawnMonsters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,211 +28,211 @@ func GetRandomEvents() []RandomEvent {
|
|||||||
return []RandomEvent{
|
return []RandomEvent{
|
||||||
{
|
{
|
||||||
Name: "altar",
|
Name: "altar",
|
||||||
Description: "You discover an ancient altar glowing with strange energy.",
|
Description: "이상한 에너지로 빛나는 고대 제단을 발견했습니다.",
|
||||||
Choices: []EventChoice{
|
Choices: []EventChoice{
|
||||||
{
|
{
|
||||||
Label: "Pray at the altar",
|
Label: "제단에서 기도하기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
if rand.Float64() < 0.6 {
|
if rand.Float64() < 0.6 {
|
||||||
heal := 15 + floor*2
|
heal := 15 + floor*2
|
||||||
return EventOutcome{HPChange: heal, Description: "The altar blesses you with healing light."}
|
return EventOutcome{HPChange: heal, Description: "제단이 치유의 빛으로 축복합니다."}
|
||||||
}
|
}
|
||||||
dmg := 10 + floor
|
dmg := 10 + floor
|
||||||
return EventOutcome{HPChange: -dmg, Description: "The altar's energy lashes out at you!"}
|
return EventOutcome{HPChange: -dmg, Description: "제단의 에너지가 당신을 공격합니다!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Offer gold",
|
Label: "골드 바치기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
cost := 10 + floor
|
cost := 10 + floor
|
||||||
return EventOutcome{GoldChange: -cost, ItemDrop: true, Description: "You offer gold and receive a divine gift."}
|
return EventOutcome{GoldChange: -cost, ItemDrop: true, Description: "골드를 바치고 신성한 선물을 받았습니다."}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Walk away",
|
Label: "그냥 지나가기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
return EventOutcome{Description: "You leave the altar undisturbed."}
|
return EventOutcome{Description: "제단을 건드리지 않고 떠납니다."}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "fountain",
|
Name: "fountain",
|
||||||
Description: "A shimmering fountain bubbles in the center of the room.",
|
Description: "방 중앙에서 빛나는 분수가 솟아오릅니다.",
|
||||||
Choices: []EventChoice{
|
Choices: []EventChoice{
|
||||||
{
|
{
|
||||||
Label: "Drink from the fountain",
|
Label: "분수의 물 마시기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
heal := 20 + floor*2
|
heal := 20 + floor*2
|
||||||
return EventOutcome{HPChange: heal, Description: "The water rejuvenates you!"}
|
return EventOutcome{HPChange: heal, Description: "물이 당신을 활기차게 합니다!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Toss a coin",
|
Label: "동전 던지기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
if rand.Float64() < 0.5 {
|
if rand.Float64() < 0.5 {
|
||||||
gold := 15 + floor*3
|
gold := 15 + floor*3
|
||||||
return EventOutcome{GoldChange: gold, Description: "The fountain rewards your generosity!"}
|
return EventOutcome{GoldChange: gold, Description: "분수가 당신의 관대함에 보답합니다!"}
|
||||||
}
|
}
|
||||||
return EventOutcome{GoldChange: -5, Description: "The coin sinks and nothing happens."}
|
return EventOutcome{GoldChange: -5, Description: "동전이 가라앉고 아무 일도 일어나지 않습니다."}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "merchant",
|
Name: "merchant",
|
||||||
Description: "A hooded merchant appears from the shadows.",
|
Description: "두건을 쓴 상인이 어둠 속에서 나타납니다.",
|
||||||
Choices: []EventChoice{
|
Choices: []EventChoice{
|
||||||
{
|
{
|
||||||
Label: "Trade gold for healing",
|
Label: "골드로 치료 거래",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
cost := 15 + floor
|
cost := 15 + floor
|
||||||
heal := 25 + floor*2
|
heal := 25 + floor*2
|
||||||
return EventOutcome{HPChange: heal, GoldChange: -cost, Description: "The merchant sells you a healing draught."}
|
return EventOutcome{HPChange: heal, GoldChange: -cost, Description: "상인이 치유의 물약을 팝니다."}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Buy a mystery item",
|
Label: "미스터리 아이템 구매",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
cost := 20 + floor*2
|
cost := 20 + floor*2
|
||||||
return EventOutcome{GoldChange: -cost, ItemDrop: true, Description: "The merchant hands you a wrapped package."}
|
return EventOutcome{GoldChange: -cost, ItemDrop: true, Description: "상인이 포장된 꾸러미를 건넵니다."}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Decline",
|
Label: "거절하기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
return EventOutcome{Description: "The merchant vanishes into the shadows."}
|
return EventOutcome{Description: "상인이 어둠 속으로 사라집니다."}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "trap_room",
|
Name: "trap_room",
|
||||||
Description: "The floor is covered with suspicious pressure plates.",
|
Description: "바닥이 수상한 압력판으로 덮여 있습니다.",
|
||||||
Choices: []EventChoice{
|
Choices: []EventChoice{
|
||||||
{
|
{
|
||||||
Label: "Carefully navigate",
|
Label: "조심히 지나가기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
if rand.Float64() < 0.5 {
|
if rand.Float64() < 0.5 {
|
||||||
return EventOutcome{Description: "You skillfully avoid all the traps!"}
|
return EventOutcome{Description: "능숙하게 모든 함정을 피했습니다!"}
|
||||||
}
|
}
|
||||||
dmg := 8 + floor
|
dmg := 8 + floor
|
||||||
return EventOutcome{HPChange: -dmg, Description: "You trigger a trap and take damage!"}
|
return EventOutcome{HPChange: -dmg, Description: "함정을 밟아 피해를 입었습니다!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Rush through",
|
Label: "돌진하기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
dmg := 5 + floor/2
|
dmg := 5 + floor/2
|
||||||
gold := 10 + floor*2
|
gold := 10 + floor*2
|
||||||
return EventOutcome{HPChange: -dmg, GoldChange: gold, Description: "You take minor damage but find hidden gold!"}
|
return EventOutcome{HPChange: -dmg, GoldChange: gold, Description: "약간의 피해를 입었지만 숨겨진 골드를 발견했습니다!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "shrine",
|
Name: "shrine",
|
||||||
Description: "A glowing shrine hums with divine power.",
|
Description: "신성한 힘으로 울리는 빛나는 성소가 있습니다.",
|
||||||
Choices: []EventChoice{
|
Choices: []EventChoice{
|
||||||
{
|
{
|
||||||
Label: "Kneel and pray",
|
Label: "무릎 꿇고 기도하기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
heal := 30 + floor*2
|
heal := 30 + floor*2
|
||||||
return EventOutcome{HPChange: heal, Description: "The shrine fills you with renewed vigor!"}
|
return EventOutcome{HPChange: heal, Description: "성소가 새로운 활력으로 가득 채워줍니다!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Take the offering",
|
Label: "제물 가져가기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
gold := 20 + floor*3
|
gold := 20 + floor*3
|
||||||
dmg := 15 + floor
|
dmg := 15 + floor
|
||||||
return EventOutcome{HPChange: -dmg, GoldChange: gold, Description: "You steal the offering but anger the spirits!"}
|
return EventOutcome{HPChange: -dmg, GoldChange: gold, Description: "제물을 훔쳤지만 영혼들이 분노합니다!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "chest",
|
Name: "chest",
|
||||||
Description: "An ornate chest sits in the corner of the room.",
|
Description: "방 구석에 화려한 상자가 놓여 있습니다.",
|
||||||
Choices: []EventChoice{
|
Choices: []EventChoice{
|
||||||
{
|
{
|
||||||
Label: "Open carefully",
|
Label: "조심히 열기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
if rand.Float64() < 0.7 {
|
if rand.Float64() < 0.7 {
|
||||||
gold := 15 + floor*2
|
gold := 15 + floor*2
|
||||||
return EventOutcome{GoldChange: gold, Description: "The chest contains a pile of gold!"}
|
return EventOutcome{GoldChange: gold, Description: "상자 안에 골드 더미가 있습니다!"}
|
||||||
}
|
}
|
||||||
dmg := 12 + floor
|
dmg := 12 + floor
|
||||||
return EventOutcome{HPChange: -dmg, Description: "The chest was a mimic! It bites you!"}
|
return EventOutcome{HPChange: -dmg, Description: "상자가 미믹이었습니다! 물어뜯깁니다!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Smash it open",
|
Label: "부수어 열기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
return EventOutcome{ItemDrop: true, Description: "You smash the chest and find equipment inside!"}
|
return EventOutcome{ItemDrop: true, Description: "상자를 부수고 안에서 장비를 발견했습니다!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Leave it",
|
Label: "그냥 두기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
return EventOutcome{Description: "Better safe than sorry."}
|
return EventOutcome{Description: "안전한 게 최고입니다."}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "ghost",
|
Name: "ghost",
|
||||||
Description: "A spectral figure materializes before you.",
|
Description: "유령 같은 형체가 눈앞에 나타납니다.",
|
||||||
Choices: []EventChoice{
|
Choices: []EventChoice{
|
||||||
{
|
{
|
||||||
Label: "Speak with the ghost",
|
Label: "유령과 대화하기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
gold := 10 + floor*2
|
gold := 10 + floor*2
|
||||||
return EventOutcome{GoldChange: gold, Description: "The ghost thanks you for listening and rewards you."}
|
return EventOutcome{GoldChange: gold, Description: "유령이 들어줘서 감사하며 보상합니다."}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Attack the ghost",
|
Label: "유령 공격하기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
if rand.Float64() < 0.4 {
|
if rand.Float64() < 0.4 {
|
||||||
return EventOutcome{ItemDrop: true, Description: "The ghost drops a spectral weapon as it fades!"}
|
return EventOutcome{ItemDrop: true, Description: "유령이 사라지며 유령 무기를 떨어뜨립니다!"}
|
||||||
}
|
}
|
||||||
dmg := 15 + floor
|
dmg := 15 + floor
|
||||||
return EventOutcome{HPChange: -dmg, Description: "The ghost retaliates with ghostly fury!"}
|
return EventOutcome{HPChange: -dmg, Description: "유령이 분노하여 반격합니다!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "mushroom",
|
Name: "mushroom",
|
||||||
Description: "Strange glowing mushrooms grow in clusters here.",
|
Description: "이상하게 빛나는 버섯들이 무리 지어 자라고 있습니다.",
|
||||||
Choices: []EventChoice{
|
Choices: []EventChoice{
|
||||||
{
|
{
|
||||||
Label: "Eat a mushroom",
|
Label: "버섯 먹기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
r := rand.Float64()
|
r := rand.Float64()
|
||||||
if r < 0.33 {
|
if r < 0.33 {
|
||||||
heal := 20 + floor*2
|
heal := 20 + floor*2
|
||||||
return EventOutcome{HPChange: heal, Description: "The mushroom tastes great and heals you!"}
|
return EventOutcome{HPChange: heal, Description: "버섯이 맛있고 치유 효과가 있습니다!"}
|
||||||
} else if r < 0.66 {
|
} else if r < 0.66 {
|
||||||
dmg := 10 + floor
|
dmg := 10 + floor
|
||||||
return EventOutcome{HPChange: -dmg, Description: "The mushroom was poisonous!"}
|
return EventOutcome{HPChange: -dmg, Description: "독버섯이었습니다!"}
|
||||||
}
|
}
|
||||||
gold := 10 + floor
|
gold := 10 + floor
|
||||||
return EventOutcome{GoldChange: gold, Description: "The mushroom gives you strange visions... and gold falls from above!"}
|
return EventOutcome{GoldChange: gold, Description: "버섯이 이상한 환각을 보여주고... 위에서 골드가 떨어집니다!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Collect and sell",
|
Label: "채집하여 팔기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
gold := 8 + floor
|
gold := 8 + floor
|
||||||
return EventOutcome{GoldChange: gold, Description: "You carefully harvest the mushrooms for sale."}
|
return EventOutcome{GoldChange: gold, Description: "조심히 버섯을 채집하여 판매합니다."}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Ignore them",
|
Label: "무시하기",
|
||||||
Resolve: func(floor int) EventOutcome {
|
Resolve: func(floor int) EventOutcome {
|
||||||
return EventOutcome{Description: "You wisely avoid the mysterious fungi."}
|
return EventOutcome{Description: "의문의 균류를 현명하게 피합니다."}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ func (s *GameSession) combatLoop() {
|
|||||||
if last, ok := s.lastActivity[p.Fingerprint]; ok {
|
if last, ok := s.lastActivity[p.Fingerprint]; ok {
|
||||||
if now.Sub(last) > 60*time.Second {
|
if now.Sub(last) > 60*time.Second {
|
||||||
slog.Warn("player inactive removed", "fingerprint", p.Fingerprint, "name", p.Name)
|
slog.Warn("player inactive removed", "fingerprint", p.Fingerprint, "name", p.Name)
|
||||||
s.addLog(fmt.Sprintf("%s removed (disconnected)", p.Name))
|
s.addLog(fmt.Sprintf("%s 제거됨 (접속 끊김)", p.Name))
|
||||||
changed = true
|
changed = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -335,15 +335,15 @@ func (s *GameSession) SubmitAction(playerID string, action PlayerAction) {
|
|||||||
desc := ""
|
desc := ""
|
||||||
switch action.Type {
|
switch action.Type {
|
||||||
case ActionAttack:
|
case ActionAttack:
|
||||||
desc = "Attacking"
|
desc = "공격"
|
||||||
case ActionSkill:
|
case ActionSkill:
|
||||||
desc = "Using Skill"
|
desc = "스킬 사용"
|
||||||
case ActionItem:
|
case ActionItem:
|
||||||
desc = "Using Item"
|
desc = "아이템 사용"
|
||||||
case ActionFlee:
|
case ActionFlee:
|
||||||
desc = "Fleeing"
|
desc = "도주"
|
||||||
case ActionWait:
|
case ActionWait:
|
||||||
desc = "Defending"
|
desc = "방어"
|
||||||
}
|
}
|
||||||
if s.state.SubmittedActions == nil {
|
if s.state.SubmittedActions == nil {
|
||||||
s.state.SubmittedActions = make(map[string]string)
|
s.state.SubmittedActions = make(map[string]string)
|
||||||
@@ -382,12 +382,12 @@ func (s *GameSession) AllocateSkillPoint(fingerprint string, branchIdx int) erro
|
|||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
if p.Fingerprint == fingerprint {
|
if p.Fingerprint == fingerprint {
|
||||||
if p.Skills == nil || p.Skills.Points <= p.Skills.Allocated {
|
if p.Skills == nil || p.Skills.Points <= p.Skills.Allocated {
|
||||||
return fmt.Errorf("no skill points available")
|
return fmt.Errorf("스킬 포인트가 없습니다")
|
||||||
}
|
}
|
||||||
return p.Skills.Allocate(branchIdx, p.Class)
|
return p.Skills.Allocate(branchIdx, p.Class)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("player not found")
|
return fmt.Errorf("플레이어를 찾을 수 없습니다")
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuyResult describes the outcome of a shop purchase attempt.
|
// BuyResult describes the outcome of a shop purchase attempt.
|
||||||
|
|||||||
72
game/turn.go
72
game/turn.go
@@ -99,13 +99,13 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
bonus := int(float64(e.Value) * (theme.DamageMult - 1.0))
|
bonus := int(float64(e.Value) * (theme.DamageMult - 1.0))
|
||||||
if bonus > 0 {
|
if bonus > 0 {
|
||||||
p.TakeDamage(bonus)
|
p.TakeDamage(bonus)
|
||||||
s.addLog(fmt.Sprintf(" (%s theme: +%d damage)", theme.Name, bonus))
|
s.addLog(fmt.Sprintf(" (%s 테마: +%d 피해)", theme.Name, bonus))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.IsDead() {
|
if p.IsDead() {
|
||||||
s.addLog(fmt.Sprintf("☠ %s has fallen!", p.Name))
|
s.addLog(fmt.Sprintf("☠ %s 쓰러졌습니다!", p.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
}
|
}
|
||||||
// Frozen players skip their action
|
// Frozen players skip their action
|
||||||
if frozenPlayers[p.Fingerprint] {
|
if frozenPlayers[p.Fingerprint] {
|
||||||
s.addLog(fmt.Sprintf("%s is frozen and cannot act!", p.Name))
|
s.addLog(fmt.Sprintf("%s 동결되어 행동할 수 없습니다!", p.Name))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
action, ok := s.actions[p.Fingerprint]
|
action, ok := s.actions[p.Fingerprint]
|
||||||
@@ -146,7 +146,7 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
intentOwners = append(intentOwners, p.Name)
|
intentOwners = append(intentOwners, p.Name)
|
||||||
case ActionSkill:
|
case ActionSkill:
|
||||||
if p.SkillUses <= 0 {
|
if p.SkillUses <= 0 {
|
||||||
s.addLog(fmt.Sprintf("%s has no skill uses left!", p.Name))
|
s.addLog(fmt.Sprintf("%s 스킬 사용 횟수가 없습니다!", p.Name))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
p.SkillUses--
|
p.SkillUses--
|
||||||
@@ -158,7 +158,7 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
m.TauntTurns = 2
|
m.TauntTurns = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.addLog(fmt.Sprintf("%s used Taunt! Enemies focus on %s for 2 turns", p.Name, p.Name))
|
s.addLog(fmt.Sprintf("%s Taunt 사용! 적들이 2턴간 %s를 집중 공격합니다", p.Name, p.Name))
|
||||||
case entity.ClassMage:
|
case entity.ClassMage:
|
||||||
skillPower := 0
|
skillPower := 0
|
||||||
if p.Skills != nil {
|
if p.Skills != nil {
|
||||||
@@ -197,13 +197,13 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
}
|
}
|
||||||
before := target.HP
|
before := target.HP
|
||||||
target.Heal(healAmount)
|
target.Heal(healAmount)
|
||||||
s.addLog(fmt.Sprintf("%s healed %s for %d HP", p.Name, target.Name, target.HP-before))
|
s.addLog(fmt.Sprintf("%s이(가) %s에게 HP %d 회복", p.Name, target.Name, target.HP-before))
|
||||||
case entity.ClassRogue:
|
case entity.ClassRogue:
|
||||||
currentRoom := s.state.Floor.Rooms[s.state.Floor.CurrentRoom]
|
currentRoom := s.state.Floor.Rooms[s.state.Floor.CurrentRoom]
|
||||||
for _, neighborIdx := range currentRoom.Neighbors {
|
for _, neighborIdx := range currentRoom.Neighbors {
|
||||||
s.state.Floor.Rooms[neighborIdx].Visited = true
|
s.state.Floor.Rooms[neighborIdx].Visited = true
|
||||||
}
|
}
|
||||||
s.addLog(fmt.Sprintf("%s scouted nearby rooms!", p.Name))
|
s.addLog(fmt.Sprintf("%s 주변 방을 정찰했습니다!", p.Name))
|
||||||
}
|
}
|
||||||
case ActionItem:
|
case ActionItem:
|
||||||
found := false
|
found := false
|
||||||
@@ -216,17 +216,17 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
}
|
}
|
||||||
p.Heal(healAmt)
|
p.Heal(healAmt)
|
||||||
p.Inventory = append(p.Inventory[:i], p.Inventory[i+1:]...)
|
p.Inventory = append(p.Inventory[:i], p.Inventory[i+1:]...)
|
||||||
s.addLog(fmt.Sprintf("%s used %s, restored %d HP", p.Name, item.Name, p.HP-before))
|
s.addLog(fmt.Sprintf("%s %s 사용, HP %d 회복", p.Name, item.Name, p.HP-before))
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
s.addLog(fmt.Sprintf("%s has no items to use!", p.Name))
|
s.addLog(fmt.Sprintf("%s 사용할 아이템이 없습니다!", p.Name))
|
||||||
}
|
}
|
||||||
case ActionFlee:
|
case ActionFlee:
|
||||||
if combat.AttemptFlee(s.cfg.Combat.FleeChance) {
|
if combat.AttemptFlee(s.cfg.Combat.FleeChance) {
|
||||||
s.addLog(fmt.Sprintf("%s fled from battle!", p.Name))
|
s.addLog(fmt.Sprintf("%s 전투에서 도주했습니다!", p.Name))
|
||||||
s.state.FleeSucceeded = true
|
s.state.FleeSucceeded = true
|
||||||
if s.state.SoloMode {
|
if s.state.SoloMode {
|
||||||
s.state.Phase = PhaseExploring
|
s.state.Phase = PhaseExploring
|
||||||
@@ -234,10 +234,10 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
}
|
}
|
||||||
p.Fled = true
|
p.Fled = true
|
||||||
} else {
|
} else {
|
||||||
s.addLog(fmt.Sprintf("%s failed to flee!", p.Name))
|
s.addLog(fmt.Sprintf("%s 도주에 실패했습니다!", p.Name))
|
||||||
}
|
}
|
||||||
case ActionWait:
|
case ActionWait:
|
||||||
s.addLog(fmt.Sprintf("%s is defending", p.Name))
|
s.addLog(fmt.Sprintf("%s 방어 중", p.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
if allFled && !s.state.SoloMode {
|
if allFled && !s.state.SoloMode {
|
||||||
s.state.Phase = PhaseExploring
|
s.state.Phase = PhaseExploring
|
||||||
s.state.Floor.Rooms[s.state.Floor.CurrentRoom].Cleared = true
|
s.state.Floor.Rooms[s.state.Floor.CurrentRoom].Cleared = true
|
||||||
s.addLog("All players fled!")
|
s.addLog("모든 플레이어가 도주했습니다!")
|
||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
p.Fled = false
|
p.Fled = false
|
||||||
}
|
}
|
||||||
@@ -307,16 +307,16 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
if r.IsAoE {
|
if r.IsAoE {
|
||||||
coopStr := ""
|
coopStr := ""
|
||||||
if r.CoopApplied {
|
if r.CoopApplied {
|
||||||
coopStr = " (co-op!)"
|
coopStr = " (협동!)"
|
||||||
}
|
}
|
||||||
s.addLog(fmt.Sprintf("%s hit all enemies for %d total dmg%s", owner, r.Damage, coopStr))
|
s.addLog(fmt.Sprintf("%s 전체 적에게 총 %d 피해%s", owner, r.Damage, coopStr))
|
||||||
} else if r.TargetIdx >= 0 && r.TargetIdx < len(s.state.Monsters) {
|
} else if r.TargetIdx >= 0 && r.TargetIdx < len(s.state.Monsters) {
|
||||||
target := s.state.Monsters[r.TargetIdx]
|
target := s.state.Monsters[r.TargetIdx]
|
||||||
coopStr := ""
|
coopStr := ""
|
||||||
if r.CoopApplied {
|
if r.CoopApplied {
|
||||||
coopStr = " (co-op!)"
|
coopStr = " (협동!)"
|
||||||
}
|
}
|
||||||
s.addLog(fmt.Sprintf("%s hit %s for %d dmg%s", owner, target.Name, r.Damage, coopStr))
|
s.addLog(fmt.Sprintf("%s이(가) %s에게 %d 피해%s", owner, target.Name, r.Damage, coopStr))
|
||||||
}
|
}
|
||||||
// Apply Life Siphon relic: heal percentage of damage dealt
|
// Apply Life Siphon relic: heal percentage of damage dealt
|
||||||
if r.Damage > 0 {
|
if r.Damage > 0 {
|
||||||
@@ -326,7 +326,7 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
heal := r.Damage * rel.Value / 100
|
heal := r.Damage * rel.Value / 100
|
||||||
if heal > 0 {
|
if heal > 0 {
|
||||||
p.Heal(heal)
|
p.Heal(heal)
|
||||||
s.addLog(fmt.Sprintf(" %s's Life Siphon heals %d HP", p.Name, heal))
|
s.addLog(fmt.Sprintf(" %s의 Life Siphon으로 HP %d 회복", p.Name, heal))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,13 +359,13 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
}
|
}
|
||||||
if r.Effect == entity.RelicHealOnKill {
|
if r.Effect == entity.RelicHealOnKill {
|
||||||
p.Heal(r.Value)
|
p.Heal(r.Value)
|
||||||
s.addLog(fmt.Sprintf("%s's relic heals %d HP", p.Name, r.Value))
|
s.addLog(fmt.Sprintf("%s의 유물로 HP %d 회복", p.Name, r.Value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Gold += goldReward + bonus
|
p.Gold += goldReward + bonus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.addLog(fmt.Sprintf("%s defeated! +%d gold", m.Name, goldReward))
|
s.addLog(fmt.Sprintf("%s 처치! +%d 골드", m.Name, goldReward))
|
||||||
if m.IsBoss {
|
if m.IsBoss {
|
||||||
s.state.BossKilled = true
|
s.state.BossKilled = true
|
||||||
s.grantBossRelic()
|
s.grantBossRelic()
|
||||||
@@ -385,7 +385,7 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
// Check if combat is over
|
// Check if combat is over
|
||||||
if len(s.state.Monsters) == 0 {
|
if len(s.state.Monsters) == 0 {
|
||||||
s.state.Floor.Rooms[s.state.Floor.CurrentRoom].Cleared = true
|
s.state.Floor.Rooms[s.state.Floor.CurrentRoom].Cleared = true
|
||||||
s.addLog("Room cleared!")
|
s.addLog("방 클리어!")
|
||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
p.Fled = false
|
p.Fled = false
|
||||||
}
|
}
|
||||||
@@ -402,7 +402,7 @@ func (s *GameSession) advanceFloor() {
|
|||||||
s.state.Phase = PhaseResult
|
s.state.Phase = PhaseResult
|
||||||
s.state.Victory = true
|
s.state.Victory = true
|
||||||
s.state.GameOver = true
|
s.state.GameOver = true
|
||||||
s.addLog("You conquered the Catacombs!")
|
s.addLog("카타콤을 정복했습니다!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Grant 1 skill point per floor clear
|
// Grant 1 skill point per floor clear
|
||||||
@@ -421,11 +421,11 @@ func (s *GameSession) advanceFloor() {
|
|||||||
}
|
}
|
||||||
s.state.Phase = PhaseExploring
|
s.state.Phase = PhaseExploring
|
||||||
s.state.CombatTurn = 0
|
s.state.CombatTurn = 0
|
||||||
s.addLog(fmt.Sprintf("Descending to B%d...", s.state.FloorNum))
|
s.addLog(fmt.Sprintf("B%d층으로 내려갑니다...", s.state.FloorNum))
|
||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
if p.IsDead() {
|
if p.IsDead() {
|
||||||
p.Revive(0.30)
|
p.Revive(0.30)
|
||||||
s.addLog(fmt.Sprintf("✦ %s revived at %d HP!", p.Name, p.HP))
|
s.addLog(fmt.Sprintf("✦ %s HP %d로 부활!", p.Name, p.HP))
|
||||||
}
|
}
|
||||||
p.Fled = false
|
p.Fled = false
|
||||||
}
|
}
|
||||||
@@ -445,7 +445,7 @@ func (s *GameSession) grantBossRelic() {
|
|||||||
if !p.IsOut() {
|
if !p.IsOut() {
|
||||||
r := relics[rand.Intn(len(relics))]
|
r := relics[rand.Intn(len(relics))]
|
||||||
p.Relics = append(p.Relics, r)
|
p.Relics = append(p.Relics, r)
|
||||||
s.addLog(fmt.Sprintf("%s obtained relic: %s", p.Name, r.Name))
|
s.addLog(fmt.Sprintf("%s 유물 획득: %s", p.Name, r.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,9 +464,9 @@ func (s *GameSession) resolveMonsterActions() {
|
|||||||
if !p.IsOut() {
|
if !p.IsOut() {
|
||||||
dmg := combat.CalcDamage(m.ATK, p.EffectiveDEF(), 0.5)
|
dmg := combat.CalcDamage(m.ATK, p.EffectiveDEF(), 0.5)
|
||||||
p.TakeDamage(dmg)
|
p.TakeDamage(dmg)
|
||||||
s.addLog(fmt.Sprintf("%s AoE hits %s for %d dmg", m.Name, p.Name, dmg))
|
s.addLog(fmt.Sprintf("%s 광역 공격으로 %s에게 %d 피해", m.Name, p.Name, dmg))
|
||||||
if p.IsDead() {
|
if p.IsDead() {
|
||||||
s.addLog(fmt.Sprintf("☠ %s has fallen!", p.Name))
|
s.addLog(fmt.Sprintf("☠ %s 쓰러졌습니다!", p.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,21 +477,21 @@ func (s *GameSession) resolveMonsterActions() {
|
|||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
if !p.IsOut() {
|
if !p.IsOut() {
|
||||||
p.AddEffect(entity.ActiveEffect{Type: entity.StatusPoison, Duration: 3, Value: 5})
|
p.AddEffect(entity.ActiveEffect{Type: entity.StatusPoison, Duration: 3, Value: 5})
|
||||||
s.addLog(fmt.Sprintf("%s poisons %s!", m.Name, p.Name))
|
s.addLog(fmt.Sprintf("%s이(가) %s에게 독을 걸었습니다!", m.Name, p.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case entity.PatternBurn:
|
case entity.PatternBurn:
|
||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
if !p.IsOut() {
|
if !p.IsOut() {
|
||||||
p.AddEffect(entity.ActiveEffect{Type: entity.StatusBurn, Duration: 2, Value: 8})
|
p.AddEffect(entity.ActiveEffect{Type: entity.StatusBurn, Duration: 2, Value: 8})
|
||||||
s.addLog(fmt.Sprintf("%s burns %s!", m.Name, p.Name))
|
s.addLog(fmt.Sprintf("%s이(가) %s을(를) 불태웠습니다!", m.Name, p.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case entity.PatternFreeze:
|
case entity.PatternFreeze:
|
||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
if !p.IsOut() {
|
if !p.IsOut() {
|
||||||
p.AddEffect(entity.ActiveEffect{Type: entity.StatusFreeze, Duration: 1, Value: 0})
|
p.AddEffect(entity.ActiveEffect{Type: entity.StatusFreeze, Duration: 1, Value: 0})
|
||||||
s.addLog(fmt.Sprintf("%s freezes %s!", m.Name, p.Name))
|
s.addLog(fmt.Sprintf("%s이(가) %s을(를) 동결시켰습니다!", m.Name, p.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case entity.PatternHeal:
|
case entity.PatternHeal:
|
||||||
@@ -500,7 +500,7 @@ func (s *GameSession) resolveMonsterActions() {
|
|||||||
if m.HP > m.MaxHP {
|
if m.HP > m.MaxHP {
|
||||||
m.HP = m.MaxHP
|
m.HP = m.MaxHP
|
||||||
}
|
}
|
||||||
s.addLog(fmt.Sprintf("%s regenerates %d HP!", m.Name, healAmt))
|
s.addLog(fmt.Sprintf("%s HP %d 재생!", m.Name, healAmt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -509,20 +509,20 @@ func (s *GameSession) resolveMonsterActions() {
|
|||||||
if !p.IsOut() {
|
if !p.IsOut() {
|
||||||
dmg := combat.CalcDamage(m.ATK, p.EffectiveDEF(), 1.0)
|
dmg := combat.CalcDamage(m.ATK, p.EffectiveDEF(), 1.0)
|
||||||
p.TakeDamage(dmg)
|
p.TakeDamage(dmg)
|
||||||
s.addLog(fmt.Sprintf("%s attacks %s for %d dmg", m.Name, p.Name, dmg))
|
s.addLog(fmt.Sprintf("%s이(가) %s을(를) 공격하여 %d 피해", m.Name, p.Name, dmg))
|
||||||
if m.IsElite {
|
if m.IsElite {
|
||||||
def := entity.ElitePrefixDefs[m.ElitePrefix]
|
def := entity.ElitePrefixDefs[m.ElitePrefix]
|
||||||
if def.OnHit >= 0 {
|
if def.OnHit >= 0 {
|
||||||
p.AddEffect(entity.ActiveEffect{Type: def.OnHit, Duration: 2, Value: 3})
|
p.AddEffect(entity.ActiveEffect{Type: def.OnHit, Duration: 2, Value: 3})
|
||||||
s.addLog(fmt.Sprintf("%s's %s effect afflicts %s!", m.Name, def.Name, p.Name))
|
s.addLog(fmt.Sprintf("%s의 %s 효과가 %s에게 적용!", m.Name, def.Name, p.Name))
|
||||||
} else if m.ElitePrefix == entity.PrefixVampiric {
|
} else if m.ElitePrefix == entity.PrefixVampiric {
|
||||||
heal := dmg / 4
|
heal := dmg / 4
|
||||||
m.HP = min(m.HP+heal, m.MaxHP)
|
m.HP = min(m.HP+heal, m.MaxHP)
|
||||||
s.addLog(fmt.Sprintf("%s drains life from %s! (+%d HP)", m.Name, p.Name, heal))
|
s.addLog(fmt.Sprintf("%s이(가) %s의 생명력을 흡수! (+%d HP)", m.Name, p.Name, heal))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p.IsDead() {
|
if p.IsDead() {
|
||||||
s.addLog(fmt.Sprintf("☠ %s has fallen!", p.Name))
|
s.addLog(fmt.Sprintf("☠ %s 쓰러졌습니다!", p.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -540,6 +540,6 @@ func (s *GameSession) resolveMonsterActions() {
|
|||||||
if allPlayersDead {
|
if allPlayersDead {
|
||||||
s.state.Phase = PhaseResult
|
s.state.Phase = PhaseResult
|
||||||
s.state.GameOver = true
|
s.state.GameOver = true
|
||||||
s.addLog("Party wiped!")
|
s.addLog("파티가 전멸했습니다!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ type Achievement struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var AchievementDefs = []Achievement{
|
var AchievementDefs = []Achievement{
|
||||||
{ID: "first_clear", Name: "Dungeon Delver", Description: "Clear floor 5 for the first time"},
|
{ID: "first_clear", Name: "던전 탐험가", Description: "처음으로 5층 클리어"},
|
||||||
{ID: "boss_slayer", Name: "Boss Slayer", Description: "Defeat any boss"},
|
{ID: "boss_slayer", Name: "보스 슬레이어", Description: "보스 처치"},
|
||||||
{ID: "floor10", Name: "Deep Explorer", Description: "Reach floor 10"},
|
{ID: "floor10", Name: "심층 탐험가", Description: "10층 도달"},
|
||||||
{ID: "floor20", Name: "Conqueror", Description: "Conquer the Catacombs (floor 20)"},
|
{ID: "floor20", Name: "정복자", Description: "카타콤 정복 (20층)"},
|
||||||
{ID: "solo_clear", Name: "Lone Wolf", Description: "Clear floor 5 solo"},
|
{ID: "solo_clear", Name: "외로운 늑대", Description: "솔로로 5층 클리어"},
|
||||||
{ID: "gold_hoarder", Name: "Gold Hoarder", Description: "Accumulate 200+ gold in one run"},
|
{ID: "gold_hoarder", Name: "골드 수집가", Description: "한 번의 플레이에서 골드 200 이상 모으기"},
|
||||||
{ID: "no_death", Name: "Untouchable", Description: "Complete a floor without anyone dying"},
|
{ID: "no_death", Name: "무적", Description: "아무도 죽지 않고 층 클리어"},
|
||||||
{ID: "full_party", Name: "Fellowship", Description: "Start a game with 4 players"},
|
{ID: "full_party", Name: "동료들", Description: "4명으로 게임 시작"},
|
||||||
{ID: "relic_collector", Name: "Relic Collector", Description: "Collect 3+ relics in one run"},
|
{ID: "relic_collector", Name: "유물 수집가", Description: "한 번의 플레이에서 유물 3개 이상 수집"},
|
||||||
{ID: "flee_master", Name: "Tactical Retreat", Description: "Successfully flee from combat"},
|
{ID: "flee_master", Name: "전략적 후퇴", Description: "전투에서 도주 성공"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DB) initAchievements() error {
|
func (d *DB) initAchievements() error {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func (s *AchievementsScreen) View(ctx *Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderAchievements(playerName string, achievements []store.Achievement, width, height int) string {
|
func renderAchievements(playerName string, achievements []store.Achievement, width, height int) string {
|
||||||
title := styleHeader.Render("── Achievements ──")
|
title := styleHeader.Render("── 업적 ──")
|
||||||
|
|
||||||
var content string
|
var content string
|
||||||
unlocked := 0
|
unlocked := 0
|
||||||
@@ -49,9 +49,9 @@ func renderAchievements(playerName string, achievements []store.Achievement, wid
|
|||||||
content += styleSystem.Render(" "+a.Description) + "\n"
|
content += styleSystem.Render(" "+a.Description) + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
progress := fmt.Sprintf("\n %s", styleGold.Render(fmt.Sprintf("%d/%d Unlocked", unlocked, len(achievements))))
|
progress := fmt.Sprintf("\n %s", styleGold.Render(fmt.Sprintf("%d/%d 해금", unlocked, len(achievements))))
|
||||||
|
|
||||||
footer := styleSystem.Render("\n[A] Back")
|
footer := styleSystem.Render("\n[A] 뒤로")
|
||||||
|
|
||||||
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
||||||
lipgloss.JoinVertical(lipgloss.Center, title, "", content, progress, footer))
|
lipgloss.JoinVertical(lipgloss.Center, title, "", content, progress, footer))
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ var classOptions = []struct {
|
|||||||
name string
|
name string
|
||||||
desc string
|
desc string
|
||||||
}{
|
}{
|
||||||
{entity.ClassWarrior, "Warrior", "HP:120 ATK:12 DEF:8 Skill: Taunt (draw enemy fire)"},
|
{entity.ClassWarrior, "Warrior", "HP:120 ATK:12 DEF:8 스킬: Taunt (적의 공격을 끌어옴)"},
|
||||||
{entity.ClassMage, "Mage", "HP:70 ATK:20 DEF:3 Skill: Fireball (AoE damage)"},
|
{entity.ClassMage, "Mage", "HP:70 ATK:20 DEF:3 스킬: Fireball (광역 피해)"},
|
||||||
{entity.ClassHealer, "Healer", "HP:90 ATK:8 DEF:5 Skill: Heal (restore 30 HP)"},
|
{entity.ClassHealer, "Healer", "HP:90 ATK:8 DEF:5 스킬: Heal (HP 30 회복)"},
|
||||||
{entity.ClassRogue, "Rogue", "HP:85 ATK:15 DEF:4 Skill: Scout (reveal rooms)"},
|
{entity.ClassRogue, "Rogue", "HP:85 ATK:15 DEF:4 스킬: Scout (주변 방 탐색)"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderClassSelect(state classSelectState, width, height int) string {
|
func renderClassSelect(state classSelectState, width, height int) string {
|
||||||
@@ -90,7 +90,7 @@ func renderClassSelect(state classSelectState, width, height int) string {
|
|||||||
descStyle := lipgloss.NewStyle().
|
descStyle := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("240"))
|
Foreground(lipgloss.Color("240"))
|
||||||
|
|
||||||
header := headerStyle.Render("── Choose Your Class ──")
|
header := headerStyle.Render("── 직업을 선택하세요 ──")
|
||||||
list := ""
|
list := ""
|
||||||
for i, opt := range classOptions {
|
for i, opt := range classOptions {
|
||||||
marker := " "
|
marker := " "
|
||||||
@@ -103,7 +103,7 @@ func renderClassSelect(state classSelectState, width, height int) string {
|
|||||||
marker, style.Render(opt.name), descStyle.Render(opt.desc))
|
marker, style.Render(opt.name), descStyle.Render(opt.desc))
|
||||||
}
|
}
|
||||||
|
|
||||||
menu := "[Up/Down] Select [Enter] Confirm"
|
menu := "[Up/Down] 선택 [Enter] 확인"
|
||||||
|
|
||||||
return lipgloss.JoinVertical(lipgloss.Left,
|
return lipgloss.JoinVertical(lipgloss.Left,
|
||||||
header,
|
header,
|
||||||
|
|||||||
@@ -71,10 +71,10 @@ func (s *CodexScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CodexScreen) View(ctx *Context) string {
|
func (s *CodexScreen) View(ctx *Context) string {
|
||||||
title := styleHeader.Render("-- Codex --")
|
title := styleHeader.Render("-- 도감 --")
|
||||||
|
|
||||||
// Tab headers
|
// Tab headers
|
||||||
tabNames := []string{"Monsters", "Items", "Events"}
|
tabNames := []string{"몬스터", "아이템", "이벤트"}
|
||||||
var tabs []string
|
var tabs []string
|
||||||
for i, name := range tabNames {
|
for i, name := range tabNames {
|
||||||
if i == s.tab {
|
if i == s.tab {
|
||||||
@@ -117,7 +117,7 @@ func (s *CodexScreen) View(ctx *Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
completion := lipgloss.NewStyle().Foreground(colorCyan).
|
completion := lipgloss.NewStyle().Foreground(colorCyan).
|
||||||
Render(fmt.Sprintf("Discovered: %d/%d (%.0f%%)", count, total, pct))
|
Render(fmt.Sprintf("발견: %d/%d (%.0f%%)", count, total, pct))
|
||||||
|
|
||||||
// Sort discovered keys for consistent display
|
// Sort discovered keys for consistent display
|
||||||
discoveredKeys := make([]string, 0, len(discovered))
|
discoveredKeys := make([]string, 0, len(discovered))
|
||||||
@@ -151,7 +151,7 @@ func (s *CodexScreen) View(ctx *Context) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer := styleSystem.Render("[Tab/Left/Right] Switch Tab [Esc] Back")
|
footer := styleSystem.Render("[Tab/Left/Right] 탭 전환 [Esc] 뒤로")
|
||||||
|
|
||||||
content := lipgloss.JoinVertical(lipgloss.Center,
|
content := lipgloss.JoinVertical(lipgloss.Center,
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ func renderMap(floor *dungeon.Floor) string {
|
|||||||
}
|
}
|
||||||
total := len(floor.Rooms)
|
total := len(floor.Rooms)
|
||||||
|
|
||||||
header := headerStyle.Render(fmt.Sprintf("── Catacombs B%d: %s ── %d/%d Rooms ──", floor.Number, theme.Name, explored, total))
|
header := headerStyle.Render(fmt.Sprintf("── 카타콤 B%d: %s ── %d/%d 방 ──", floor.Number, theme.Name, explored, total))
|
||||||
return header + "\n" + dungeon.RenderFloor(floor, floor.CurrentRoom, true)
|
return header + "\n" + dungeon.RenderFloor(floor, floor.CurrentRoom, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,16 +401,16 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
|
|||||||
hpBar := renderHPBar(p.HP, p.MaxHP, 20)
|
hpBar := renderHPBar(p.HP, p.MaxHP, 20)
|
||||||
status := ""
|
status := ""
|
||||||
if p.IsDead() {
|
if p.IsDead() {
|
||||||
status = " [DEAD]"
|
status = " [사망]"
|
||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf("%s (%s) %s %d/%d%s Gold: %d",
|
sb.WriteString(fmt.Sprintf("%s (%s) %s %d/%d%s 골드: %d",
|
||||||
p.Name, p.Class, hpBar, p.HP, p.MaxHP, status, p.Gold))
|
p.Name, p.Class, hpBar, p.HP, p.MaxHP, status, p.Gold))
|
||||||
|
|
||||||
// Show inventory count
|
// Show inventory count
|
||||||
itemCount := len(p.Inventory)
|
itemCount := len(p.Inventory)
|
||||||
relicCount := len(p.Relics)
|
relicCount := len(p.Relics)
|
||||||
if itemCount > 0 || relicCount > 0 {
|
if itemCount > 0 || relicCount > 0 {
|
||||||
sb.WriteString(fmt.Sprintf(" Items:%d Relics:%d", itemCount, relicCount))
|
sb.WriteString(fmt.Sprintf(" 아이템:%d 유물:%d", itemCount, relicCount))
|
||||||
}
|
}
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
@@ -439,7 +439,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
|
|||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
|
|
||||||
// Action bar
|
// Action bar
|
||||||
sb.WriteString(styleAction.Render("[1]Attack [2]Skill [3]Item [4]Flee [5]Wait [Tab]Target [/]Chat"))
|
sb.WriteString(styleAction.Render("[1]공격 [2]스킬 [3]아이템 [4]도주 [5]대기 [Tab]대상 [/]채팅"))
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
|
|
||||||
// Timer
|
// Timer
|
||||||
@@ -448,7 +448,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
|
|||||||
if remaining < 0 {
|
if remaining < 0 {
|
||||||
remaining = 0
|
remaining = 0
|
||||||
}
|
}
|
||||||
sb.WriteString(styleTimer.Render(fmt.Sprintf(" Timer: %.1fs", remaining.Seconds())))
|
sb.WriteString(styleTimer.Render(fmt.Sprintf(" 타이머: %.1f초", remaining.Seconds())))
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,15 +458,15 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
|
|||||||
var skillDesc string
|
var skillDesc string
|
||||||
switch p.Class {
|
switch p.Class {
|
||||||
case entity.ClassWarrior:
|
case entity.ClassWarrior:
|
||||||
skillDesc = "Skill: Taunt — enemies attack you for 2 turns"
|
skillDesc = "스킬: Taunt — 2턴간 적의 공격을 끌어옴"
|
||||||
case entity.ClassMage:
|
case entity.ClassMage:
|
||||||
skillDesc = "Skill: Fireball — AoE 0.8x dmg to all enemies"
|
skillDesc = "스킬: Fireball — 전체 적에게 0.8배 피해"
|
||||||
case entity.ClassHealer:
|
case entity.ClassHealer:
|
||||||
skillDesc = "Skill: Heal — restore 30 HP to an ally"
|
skillDesc = "스킬: Heal — 아군 HP 30 회복"
|
||||||
case entity.ClassRogue:
|
case entity.ClassRogue:
|
||||||
skillDesc = "Skill: Scout — reveal neighboring rooms"
|
skillDesc = "스킬: Scout — 주변 방 공개"
|
||||||
}
|
}
|
||||||
skillDesc += fmt.Sprintf(" (%d uses left)", p.SkillUses)
|
skillDesc += fmt.Sprintf(" (남은 횟수: %d)", p.SkillUses)
|
||||||
sb.WriteString(styleSystem.Render(skillDesc))
|
sb.WriteString(styleSystem.Render(skillDesc))
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
break
|
break
|
||||||
@@ -484,7 +484,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
|
|||||||
r := state.Floor.Rooms[n]
|
r := state.Floor.Rooms[n]
|
||||||
status := r.Type.String()
|
status := r.Type.String()
|
||||||
if r.Cleared {
|
if r.Cleared {
|
||||||
status = "Cleared"
|
status = "클리어"
|
||||||
}
|
}
|
||||||
marker := " "
|
marker := " "
|
||||||
style := normalStyle
|
style := normalStyle
|
||||||
@@ -492,7 +492,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
|
|||||||
marker = "> "
|
marker = "> "
|
||||||
style = selectedStyle
|
style = selectedStyle
|
||||||
}
|
}
|
||||||
sb.WriteString(style.Render(fmt.Sprintf("%sRoom %d: %s", marker, n, status)))
|
sb.WriteString(style.Render(fmt.Sprintf("%s방 %d: %s", marker, n, status)))
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -504,7 +504,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
|
|||||||
branches := entity.GetBranches(p.Class)
|
branches := entity.GetBranches(p.Class)
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
skillStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("213")).Bold(true)
|
skillStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("213")).Bold(true)
|
||||||
sb.WriteString(skillStyle.Render(fmt.Sprintf(" Skill Point Available! (%d unspent)", p.Skills.Points-p.Skills.Allocated)))
|
sb.WriteString(skillStyle.Render(fmt.Sprintf(" 스킬 포인트 사용 가능! (미사용: %d)", p.Skills.Points-p.Skills.Allocated)))
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
for i, branch := range branches {
|
for i, branch := range branches {
|
||||||
key := "["
|
key := "["
|
||||||
@@ -513,7 +513,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
|
|||||||
}
|
}
|
||||||
nextNode := p.Skills.Allocated
|
nextNode := p.Skills.Allocated
|
||||||
if p.Skills.BranchIndex >= 0 && p.Skills.BranchIndex != i {
|
if p.Skills.BranchIndex >= 0 && p.Skills.BranchIndex != i {
|
||||||
sb.WriteString(fmt.Sprintf(" [%s] %s (locked)\n", key, branch.Name))
|
sb.WriteString(fmt.Sprintf(" [%s] %s (잠김)\n", key, branch.Name))
|
||||||
} else if nextNode < 3 {
|
} else if nextNode < 3 {
|
||||||
node := branch.Nodes[nextNode]
|
node := branch.Nodes[nextNode]
|
||||||
sb.WriteString(fmt.Sprintf(" [%s] %s -> %s\n", key, branch.Name, node.Name))
|
sb.WriteString(fmt.Sprintf(" [%s] %s -> %s\n", key, branch.Name, node.Name))
|
||||||
@@ -522,7 +522,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.WriteString("[Up/Down] Select [Enter] Move [Q] Quit")
|
sb.WriteString("[Up/Down] 선택 [Enter] 이동 [Q] 종료")
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.Phase == game.PhaseCombat {
|
if state.Phase == game.PhaseCombat {
|
||||||
@@ -550,19 +550,19 @@ func renderCombatLog(log []string) string {
|
|||||||
|
|
||||||
func colorizeLog(msg string) string {
|
func colorizeLog(msg string) string {
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(msg, "fled"):
|
case strings.Contains(msg, "도주"):
|
||||||
return styleFlee.Render(msg)
|
return styleFlee.Render(msg)
|
||||||
case strings.Contains(msg, "co-op"):
|
case strings.Contains(msg, "협동"):
|
||||||
return styleCoop.Render(msg)
|
return styleCoop.Render(msg)
|
||||||
case strings.Contains(msg, "healed") || strings.Contains(msg, "Heal") || strings.Contains(msg, "Blessing"):
|
case strings.Contains(msg, "회복") || strings.Contains(msg, "Heal") || strings.Contains(msg, "치유") || strings.Contains(msg, "부활"):
|
||||||
return styleHeal.Render(msg)
|
return styleHeal.Render(msg)
|
||||||
case strings.Contains(msg, "dmg") || strings.Contains(msg, "hit") || strings.Contains(msg, "attacks") || strings.Contains(msg, "Trap"):
|
case strings.Contains(msg, "피해") || strings.Contains(msg, "공격") || strings.Contains(msg, "Trap") || strings.Contains(msg, "함정"):
|
||||||
return styleDamage.Render(msg)
|
return styleDamage.Render(msg)
|
||||||
case strings.Contains(msg, "Taunt") || strings.Contains(msg, "scouted"):
|
case strings.Contains(msg, "Taunt") || strings.Contains(msg, "정찰"):
|
||||||
return styleStatus.Render(msg)
|
return styleStatus.Render(msg)
|
||||||
case strings.Contains(msg, "gold") || strings.Contains(msg, "Gold") || strings.Contains(msg, "found"):
|
case strings.Contains(msg, "골드") || strings.Contains(msg, "Gold") || strings.Contains(msg, "발견"):
|
||||||
return styleGold.Render(msg)
|
return styleGold.Render(msg)
|
||||||
case strings.Contains(msg, "defeated") || strings.Contains(msg, "cleared") || strings.Contains(msg, "Descending"):
|
case strings.Contains(msg, "처치") || strings.Contains(msg, "클리어") || strings.Contains(msg, "내려갑니다") || strings.Contains(msg, "정복"):
|
||||||
return styleSystem.Render(msg)
|
return styleSystem.Render(msg)
|
||||||
default:
|
default:
|
||||||
return msg
|
return msg
|
||||||
@@ -600,14 +600,14 @@ func renderHPBar(current, max, width int) string {
|
|||||||
|
|
||||||
func renderPartyPanel(players []*entity.Player, submittedActions map[string]string) string {
|
func renderPartyPanel(players []*entity.Player, submittedActions map[string]string) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString(styleHeader.Render(" PARTY") + "\n\n")
|
sb.WriteString(styleHeader.Render(" 아군") + "\n\n")
|
||||||
|
|
||||||
for _, p := range players {
|
for _, p := range players {
|
||||||
nameStr := stylePlayer.Render(fmt.Sprintf(" ♦ %s", p.Name))
|
nameStr := stylePlayer.Render(fmt.Sprintf(" ♦ %s", p.Name))
|
||||||
classStr := styleSystem.Render(fmt.Sprintf(" (%s)", p.Class))
|
classStr := styleSystem.Render(fmt.Sprintf(" (%s)", p.Class))
|
||||||
status := ""
|
status := ""
|
||||||
if p.IsDead() {
|
if p.IsDead() {
|
||||||
status = styleDamage.Render(" [DEAD]")
|
status = styleDamage.Render(" [사망]")
|
||||||
}
|
}
|
||||||
sb.WriteString(nameStr + classStr + status + "\n")
|
sb.WriteString(nameStr + classStr + status + "\n")
|
||||||
|
|
||||||
@@ -637,7 +637,7 @@ func renderPartyPanel(players []*entity.Player, submittedActions map[string]stri
|
|||||||
sb.WriteString(styleHeal.Render(fmt.Sprintf(" ✓ %s", action)))
|
sb.WriteString(styleHeal.Render(fmt.Sprintf(" ✓ %s", action)))
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
} else if !p.IsOut() {
|
} else if !p.IsOut() {
|
||||||
sb.WriteString(styleSystem.Render(" ... Waiting"))
|
sb.WriteString(styleSystem.Render(" ... 대기중"))
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
@@ -647,7 +647,7 @@ func renderPartyPanel(players []*entity.Player, submittedActions map[string]stri
|
|||||||
|
|
||||||
func renderEnemyPanel(monsters []*entity.Monster, targetCursor int) string {
|
func renderEnemyPanel(monsters []*entity.Monster, targetCursor int) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString(styleHeader.Render(" ENEMIES") + "\n\n")
|
sb.WriteString(styleHeader.Render(" 적") + "\n\n")
|
||||||
|
|
||||||
for i, m := range monsters {
|
for i, m := range monsters {
|
||||||
if m.IsDead() {
|
if m.IsDead() {
|
||||||
@@ -667,7 +667,7 @@ func renderEnemyPanel(monsters []*entity.Monster, targetCursor int) string {
|
|||||||
hpBar := renderHPBar(m.HP, m.MaxHP, 12)
|
hpBar := renderHPBar(m.HP, m.MaxHP, 12)
|
||||||
taunt := ""
|
taunt := ""
|
||||||
if m.TauntTarget {
|
if m.TauntTarget {
|
||||||
taunt = styleStatus.Render(fmt.Sprintf(" [TAUNTED %dt]", m.TauntTurns))
|
taunt = styleStatus.Render(fmt.Sprintf(" [도발됨 %d턴]", m.TauntTurns))
|
||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf(" %s[%d] %s %s %d/%d%s\n\n",
|
sb.WriteString(fmt.Sprintf(" %s[%d] %s %s %d/%d%s\n\n",
|
||||||
marker, i, styleEnemy.Render(m.Name), hpBar, m.HP, m.MaxHP, taunt))
|
marker, i, styleEnemy.Render(m.Name), hpBar, m.HP, m.MaxHP, taunt))
|
||||||
|
|||||||
@@ -26,38 +26,38 @@ func (s *HelpScreen) View(ctx *Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderHelp(width, height int) string {
|
func renderHelp(width, height int) string {
|
||||||
title := styleHeader.Render("── Controls ──")
|
title := styleHeader.Render("── 조작법 ──")
|
||||||
|
|
||||||
sections := []struct{ header, body string }{
|
sections := []struct{ header, body string }{
|
||||||
{"Lobby", ` [C] Create room [J] Join by code
|
{"로비", ` [C] 방 만들기 [J] 코드로 참가
|
||||||
[Enter] Join selected room
|
[Enter] 선택한 방 참가
|
||||||
[D] Daily Challenge [H] Hard Mode toggle
|
[D] 일일 도전 [H] 하드 모드 전환
|
||||||
[Q] Back to title`},
|
[Q] 타이틀로 돌아가기`},
|
||||||
{"Exploration", ` [Up/Down] Select room
|
{"탐험", ` [Up/Down] 방 선택
|
||||||
[Enter] Move to room
|
[Enter] 방으로 이동
|
||||||
[[] / []] Allocate skill point (branch 1/2)
|
[[] / []] 스킬 포인트 배분 (분기 1/2)
|
||||||
[/] Chat
|
[/] 채팅
|
||||||
[Q] Quit`},
|
[Q] 종료`},
|
||||||
{"Combat (10s per turn)", ` [1] Attack [2] Skill
|
{"전투 (턴당 10초)", ` [1] 공격 [2] 스킬
|
||||||
[3] Use Item [4] Flee
|
[3] 아이템 사용 [4] 도주
|
||||||
[5] Defend [Tab] Switch Target
|
[5] 방어 [Tab] 대상 변경
|
||||||
[/] Chat`},
|
[/] 채팅`},
|
||||||
{"Shop", ` [1-3] Buy item
|
{"상점", ` [1-3] 아이템 구매
|
||||||
[Q] Leave shop`},
|
[Q] 상점 나가기`},
|
||||||
{"Classes", ` Warrior 120HP 12ATK 8DEF Taunt (draw fire 2t)
|
{"직업", ` Warrior 120HP 12ATK 8DEF Taunt (2턴간 적 공격 유도)
|
||||||
Mage 70HP 20ATK 3DEF Fireball (AoE 0.8x)
|
Mage 70HP 20ATK 3DEF Fireball (광역 0.8배)
|
||||||
Healer 90HP 8ATK 5DEF Heal (restore 30HP)
|
Healer 90HP 8ATK 5DEF Heal (HP 30 회복)
|
||||||
Rogue 85HP 15ATK 4DEF Scout (reveal rooms)`},
|
Rogue 85HP 15ATK 4DEF Scout (주변 방 공개)`},
|
||||||
{"Multiplayer", ` • Up to 4 players per room
|
{"멀티플레이", ` • 방당 최대 4명
|
||||||
• Co-op bonus: +10% dmg when 2+ target same enemy
|
• 협동 보너스: 2명 이상이 같은 적 공격 시 피해 +10%
|
||||||
• Class combos trigger bonus effects
|
• 직업 콤보로 추가 효과 발동
|
||||||
• All players ready → game starts`},
|
• 모든 플레이어 준비 완료 시 게임 시작`},
|
||||||
{"Tips", ` • Skills have 3 uses per combat
|
{"팁", ` • 스킬은 전투당 3회 사용 가능
|
||||||
• Items are limited to 10 per player
|
• 아이템은 플레이어당 10개 제한
|
||||||
• Dead players revive next floor at 30% HP
|
• 사망한 플레이어는 다음 층에서 HP 30%로 부활
|
||||||
• Bosses appear at floors 5, 10, 15, 20
|
• 보스는 5, 10, 15, 20층에 등장
|
||||||
• Skill points: 1 per floor clear (max 3)
|
• 스킬 포인트: 층 클리어당 1포인트 (최대 3)
|
||||||
• Weekly mutations rotate gameplay modifiers`},
|
• 주간 변이가 게임플레이를 변경`},
|
||||||
}
|
}
|
||||||
|
|
||||||
var content string
|
var content string
|
||||||
@@ -69,7 +69,7 @@ func renderHelp(width, height int) string {
|
|||||||
content += bodyStyle.Render(s.body) + "\n\n"
|
content += bodyStyle.Render(s.body) + "\n\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
footer := styleSystem.Render("[H] Back")
|
footer := styleSystem.Render("[H] 뒤로")
|
||||||
|
|
||||||
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
||||||
lipgloss.JoinVertical(lipgloss.Center, title, "", content, footer))
|
lipgloss.JoinVertical(lipgloss.Center, title, "", content, footer))
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ func (s *LeaderboardScreen) View(ctx *Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRecord, tab, width, height int) string {
|
func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRecord, tab, width, height int) string {
|
||||||
title := styleHeader.Render("── Leaderboard ──")
|
title := styleHeader.Render("── 리더보드 ──")
|
||||||
|
|
||||||
// Tab header
|
// Tab header
|
||||||
tabs := []string{"Floor", "Gold", "Daily"}
|
tabs := []string{"층수", "골드", "일일"}
|
||||||
var tabLine string
|
var tabLine string
|
||||||
for i, t := range tabs {
|
for i, t := range tabs {
|
||||||
if i == tab {
|
if i == tab {
|
||||||
@@ -60,7 +60,7 @@ func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRec
|
|||||||
|
|
||||||
switch tab {
|
switch tab {
|
||||||
case 0: // By Floor
|
case 0: // By Floor
|
||||||
content += styleCoop.Render(" Top by Floor") + "\n"
|
content += styleCoop.Render(" 층수 순위") + "\n"
|
||||||
for i, r := range byFloor {
|
for i, r := range byFloor {
|
||||||
if i >= 10 {
|
if i >= 10 {
|
||||||
break
|
break
|
||||||
@@ -75,7 +75,7 @@ func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRec
|
|||||||
r.Floor, styleGold.Render(fmt.Sprintf("%dg", r.Score)))
|
r.Floor, styleGold.Render(fmt.Sprintf("%dg", r.Score)))
|
||||||
}
|
}
|
||||||
case 1: // By Gold
|
case 1: // By Gold
|
||||||
content += styleCoop.Render(" Top by Gold") + "\n"
|
content += styleCoop.Render(" 골드 순위") + "\n"
|
||||||
for i, r := range byGold {
|
for i, r := range byGold {
|
||||||
if i >= 10 {
|
if i >= 10 {
|
||||||
break
|
break
|
||||||
@@ -90,9 +90,9 @@ func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRec
|
|||||||
r.Floor, styleGold.Render(fmt.Sprintf("%dg", r.Score)))
|
r.Floor, styleGold.Render(fmt.Sprintf("%dg", r.Score)))
|
||||||
}
|
}
|
||||||
case 2: // Daily
|
case 2: // Daily
|
||||||
content += styleCoop.Render(fmt.Sprintf(" Daily Challenge — %s", time.Now().Format("2006-01-02"))) + "\n"
|
content += styleCoop.Render(fmt.Sprintf(" 일일 도전 — %s", time.Now().Format("2006-01-02"))) + "\n"
|
||||||
if len(daily) == 0 {
|
if len(daily) == 0 {
|
||||||
content += " No daily runs yet today.\n"
|
content += " 오늘 일일 도전 기록이 없습니다.\n"
|
||||||
}
|
}
|
||||||
for i, r := range daily {
|
for i, r := range daily {
|
||||||
if i >= 20 {
|
if i >= 20 {
|
||||||
@@ -105,7 +105,7 @@ func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer := styleSystem.Render("\n[Tab] Switch Tab [L] Back")
|
footer := styleSystem.Render("\n[Tab] 탭 전환 [L] 뒤로")
|
||||||
|
|
||||||
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
||||||
lipgloss.JoinVertical(lipgloss.Center, title, tabLine, "", content, footer))
|
lipgloss.JoinVertical(lipgloss.Center, title, tabLine, "", content, footer))
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ func (s *LobbyScreen) refreshLobby(ctx *Context) {
|
|||||||
rooms := ctx.Lobby.ListRooms()
|
rooms := ctx.Lobby.ListRooms()
|
||||||
s.rooms = make([]roomInfo, len(rooms))
|
s.rooms = make([]roomInfo, len(rooms))
|
||||||
for i, r := range rooms {
|
for i, r := range rooms {
|
||||||
status := "Waiting"
|
status := "대기중"
|
||||||
if r.Status == game.RoomPlaying {
|
if r.Status == game.RoomPlaying {
|
||||||
status = "Playing"
|
status = "진행중"
|
||||||
}
|
}
|
||||||
players := make([]playerInfo, len(r.Players))
|
players := make([]playerInfo, len(r.Players))
|
||||||
for j, p := range r.Players {
|
for j, p := range r.Players {
|
||||||
@@ -108,7 +108,7 @@ func (s *LobbyScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
|
|||||||
// Normal lobby key handling
|
// Normal lobby key handling
|
||||||
if isKey(key, "c") {
|
if isKey(key, "c") {
|
||||||
if ctx.Lobby != nil {
|
if ctx.Lobby != nil {
|
||||||
code := ctx.Lobby.CreateRoom(ctx.PlayerName + "'s Room")
|
code := ctx.Lobby.CreateRoom(ctx.PlayerName + "의 방")
|
||||||
ctx.Lobby.JoinRoom(code, ctx.PlayerName, ctx.Fingerprint)
|
ctx.Lobby.JoinRoom(code, ctx.PlayerName, ctx.Fingerprint)
|
||||||
ctx.RoomCode = code
|
ctx.RoomCode = code
|
||||||
return NewClassSelectScreen(), nil
|
return NewClassSelectScreen(), nil
|
||||||
@@ -135,7 +135,7 @@ func (s *LobbyScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
|
|||||||
} else if isKey(key, "d") {
|
} else if isKey(key, "d") {
|
||||||
// Daily Challenge: create a private solo daily session
|
// Daily Challenge: create a private solo daily session
|
||||||
if ctx.Lobby != nil {
|
if ctx.Lobby != nil {
|
||||||
code := ctx.Lobby.CreateRoom(ctx.PlayerName + "'s Daily")
|
code := ctx.Lobby.CreateRoom(ctx.PlayerName + "의 일일 도전")
|
||||||
if err := ctx.Lobby.JoinRoom(code, ctx.PlayerName, ctx.Fingerprint); err == nil {
|
if err := ctx.Lobby.JoinRoom(code, ctx.PlayerName, ctx.Fingerprint); err == nil {
|
||||||
ctx.RoomCode = code
|
ctx.RoomCode = code
|
||||||
room := ctx.Lobby.GetRoom(code)
|
room := ctx.Lobby.GetRoom(code)
|
||||||
@@ -200,14 +200,14 @@ func renderLobby(state lobbyState, width, height int) string {
|
|||||||
Border(lipgloss.RoundedBorder()).
|
Border(lipgloss.RoundedBorder()).
|
||||||
Padding(0, 1)
|
Padding(0, 1)
|
||||||
|
|
||||||
header := headerStyle.Render(fmt.Sprintf("── Lobby ── %d online ──", state.online))
|
header := headerStyle.Render(fmt.Sprintf("── 로비 ── %d명 접속중 ──", state.online))
|
||||||
menu := "[C] Create Room [J] Join by Code [D] Daily Challenge [Up/Down] Select [Enter] Join [Q] Back"
|
menu := "[C] 방 만들기 [J] 코드로 참가 [D] 일일 도전 [Up/Down] 선택 [Enter] 참가 [Q] 뒤로"
|
||||||
if state.hardUnlocked {
|
if state.hardUnlocked {
|
||||||
hardStatus := "OFF"
|
hardStatus := "OFF"
|
||||||
if state.hardMode {
|
if state.hardMode {
|
||||||
hardStatus = "ON"
|
hardStatus = "ON"
|
||||||
}
|
}
|
||||||
menu += fmt.Sprintf(" [H] Hard Mode: %s", hardStatus)
|
menu += fmt.Sprintf(" [H] 하드 모드: %s", hardStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
roomList := ""
|
roomList := ""
|
||||||
@@ -234,11 +234,11 @@ func renderLobby(state lobbyState, width, height int) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if roomList == "" {
|
if roomList == "" {
|
||||||
roomList = " No rooms available. Create one!"
|
roomList = " 방이 없습니다. 새로 만드세요!"
|
||||||
}
|
}
|
||||||
if state.joining {
|
if state.joining {
|
||||||
inputStr := state.codeInput + strings.Repeat("_", 4-len(state.codeInput))
|
inputStr := state.codeInput + strings.Repeat("_", 4-len(state.codeInput))
|
||||||
roomList += fmt.Sprintf("\n Enter room code: [%s] (Esc to cancel)\n", inputStr)
|
roomList += fmt.Sprintf("\n 방 코드 입력: [%s] (Esc로 취소)\n", inputStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return lipgloss.JoinVertical(lipgloss.Left,
|
return lipgloss.JoinVertical(lipgloss.Left,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (s *NicknameScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
|
|||||||
gs := NewGameScreen()
|
gs := NewGameScreen()
|
||||||
gs.gameState = ctx.Session.GetState()
|
gs.gameState = ctx.Session.GetState()
|
||||||
ctx.Session.TouchActivity(ctx.Fingerprint)
|
ctx.Session.TouchActivity(ctx.Fingerprint)
|
||||||
ctx.Session.SendChat("System", ctx.PlayerName+" reconnected!")
|
ctx.Session.SendChat("System", ctx.PlayerName+" 재접속!")
|
||||||
return gs, gs.pollState()
|
return gs, gs.pollState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func (s *NicknameScreen) View(ctx *Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderNickname(input string, width, height int) string {
|
func renderNickname(input string, width, height int) string {
|
||||||
title := styleHeader.Render("── Enter Your Name ──")
|
title := styleHeader.Render("── 이름을 입력하세요 ──")
|
||||||
|
|
||||||
display := input
|
display := input
|
||||||
if display == "" {
|
if display == "" {
|
||||||
@@ -81,8 +81,8 @@ func renderNickname(input string, width, height int) string {
|
|||||||
Padding(0, 2).
|
Padding(0, 2).
|
||||||
Render(stylePlayer.Render(display))
|
Render(stylePlayer.Render(display))
|
||||||
|
|
||||||
hint := styleSystem.Render(fmt.Sprintf("(%d/12 characters)", len(input)))
|
hint := styleSystem.Render(fmt.Sprintf("(%d/12 글자)", len(input)))
|
||||||
footer := styleAction.Render("[Enter] Confirm [Esc] Cancel")
|
footer := styleAction.Render("[Enter] 확인 [Esc] 취소")
|
||||||
|
|
||||||
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
||||||
lipgloss.JoinVertical(lipgloss.Center, title, "", inputBox, hint, "", footer))
|
lipgloss.JoinVertical(lipgloss.Center, title, "", inputBox, hint, "", footer))
|
||||||
|
|||||||
@@ -56,30 +56,30 @@ func renderResult(state game.GameState, rankings []store.RunRecord) string {
|
|||||||
|
|
||||||
// Title
|
// Title
|
||||||
if state.Victory {
|
if state.Victory {
|
||||||
sb.WriteString(styleHeal.Render(" ✦ VICTORY ✦ ") + "\n\n")
|
sb.WriteString(styleHeal.Render(" ✦ 승리 ✦ ") + "\n\n")
|
||||||
sb.WriteString(styleSystem.Render(" You conquered the Catacombs!") + "\n\n")
|
sb.WriteString(styleSystem.Render(" 카타콤을 정복했습니다!") + "\n\n")
|
||||||
} else {
|
} else {
|
||||||
sb.WriteString(styleDamage.Render(" ✦ DEFEAT ✦ ") + "\n\n")
|
sb.WriteString(styleDamage.Render(" ✦ 패배 ✦ ") + "\n\n")
|
||||||
sb.WriteString(styleSystem.Render(fmt.Sprintf(" Fallen on floor B%d", state.FloorNum)) + "\n\n")
|
sb.WriteString(styleSystem.Render(fmt.Sprintf(" B%d층에서 쓰러졌습니다", state.FloorNum)) + "\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Player summary
|
// Player summary
|
||||||
sb.WriteString(styleHeader.Render("── Party Summary ──") + "\n\n")
|
sb.WriteString(styleHeader.Render("── 파티 요약 ──") + "\n\n")
|
||||||
totalGold := 0
|
totalGold := 0
|
||||||
for _, p := range state.Players {
|
for _, p := range state.Players {
|
||||||
status := styleHeal.Render("Alive")
|
status := styleHeal.Render("생존")
|
||||||
if p.IsDead() {
|
if p.IsDead() {
|
||||||
status = styleDamage.Render("Dead")
|
status = styleDamage.Render("사망")
|
||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf(" %s (%s) %s Gold: %d Items: %d Relics: %d\n",
|
sb.WriteString(fmt.Sprintf(" %s (%s) %s 골드: %d 아이템: %d 유물: %d\n",
|
||||||
stylePlayer.Render(p.Name), p.Class, status, p.Gold, len(p.Inventory), len(p.Relics)))
|
stylePlayer.Render(p.Name), p.Class, status, p.Gold, len(p.Inventory), len(p.Relics)))
|
||||||
totalGold += p.Gold
|
totalGold += p.Gold
|
||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf("\n Total Gold: %s\n", styleGold.Render(fmt.Sprintf("%d", totalGold))))
|
sb.WriteString(fmt.Sprintf("\n 총 골드: %s\n", styleGold.Render(fmt.Sprintf("%d", totalGold))))
|
||||||
|
|
||||||
// Rankings
|
// Rankings
|
||||||
if len(rankings) > 0 {
|
if len(rankings) > 0 {
|
||||||
sb.WriteString("\n" + styleHeader.Render("── Top Runs ──") + "\n\n")
|
sb.WriteString("\n" + styleHeader.Render("── 최고 기록 ──") + "\n\n")
|
||||||
for i, r := range rankings {
|
for i, r := range rankings {
|
||||||
medal := " "
|
medal := " "
|
||||||
switch i {
|
switch i {
|
||||||
@@ -90,11 +90,11 @@ func renderResult(state game.GameState, rankings []store.RunRecord) string {
|
|||||||
case 2:
|
case 2:
|
||||||
medal = styleGold.Render("🥉")
|
medal = styleGold.Render("🥉")
|
||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf(" %s %s Floor B%d Score: %d\n", medal, r.Player, r.Floor, r.Score))
|
sb.WriteString(fmt.Sprintf(" %s %s B%d층 점수: %d\n", medal, r.Player, r.Floor, r.Score))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString("\n" + styleAction.Render(" [Enter] Return to Lobby") + "\n")
|
sb.WriteString("\n" + styleAction.Render(" [Enter] 로비로 돌아가기") + "\n")
|
||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ func (s *ShopScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
|
|||||||
idx := int(key.String()[0] - '1')
|
idx := int(key.String()[0] - '1')
|
||||||
switch ctx.Session.BuyItem(ctx.Fingerprint, idx) {
|
switch ctx.Session.BuyItem(ctx.Fingerprint, idx) {
|
||||||
case game.BuyOK:
|
case game.BuyOK:
|
||||||
s.shopMsg = "Purchased!"
|
s.shopMsg = "구매 완료!"
|
||||||
case game.BuyNoGold:
|
case game.BuyNoGold:
|
||||||
s.shopMsg = "Not enough gold!"
|
s.shopMsg = "골드가 부족합니다!"
|
||||||
case game.BuyInventoryFull:
|
case game.BuyInventoryFull:
|
||||||
s.shopMsg = "Inventory full!"
|
s.shopMsg = "인벤토리가 가득 찼습니다!"
|
||||||
default:
|
default:
|
||||||
s.shopMsg = "Cannot buy that!"
|
s.shopMsg = "구매할 수 없습니다!"
|
||||||
}
|
}
|
||||||
s.gameState = ctx.Session.GetState()
|
s.gameState = ctx.Session.GetState()
|
||||||
}
|
}
|
||||||
@@ -76,23 +76,23 @@ func renderShop(state game.GameState, width, height int, shopMsg string) string
|
|||||||
Foreground(lipgloss.Color("196")).
|
Foreground(lipgloss.Color("196")).
|
||||||
Bold(true)
|
Bold(true)
|
||||||
|
|
||||||
header := headerStyle.Render("── Shop ──")
|
header := headerStyle.Render("── 상점 ──")
|
||||||
|
|
||||||
// Show current player's gold
|
// Show current player's gold
|
||||||
goldLine := ""
|
goldLine := ""
|
||||||
for _, p := range state.Players {
|
for _, p := range state.Players {
|
||||||
inventoryCount := len(p.Inventory)
|
inventoryCount := len(p.Inventory)
|
||||||
goldLine += goldStyle.Render(fmt.Sprintf(" %s — Gold: %d Items: %d/10", p.Name, p.Gold, inventoryCount))
|
goldLine += goldStyle.Render(fmt.Sprintf(" %s — 골드: %d 아이템: %d/10", p.Name, p.Gold, inventoryCount))
|
||||||
goldLine += "\n"
|
goldLine += "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
items := ""
|
items := ""
|
||||||
for i, item := range state.ShopItems {
|
for i, item := range state.ShopItems {
|
||||||
label := itemTypeLabel(item)
|
label := itemTypeLabel(item)
|
||||||
items += fmt.Sprintf(" [%d] %s %s — %d gold\n", i+1, item.Name, label, item.Price)
|
items += fmt.Sprintf(" [%d] %s %s — %d 골드\n", i+1, item.Name, label, item.Price)
|
||||||
}
|
}
|
||||||
|
|
||||||
menu := "[1-3] Buy [Q] Leave Shop"
|
menu := "[1-3] 구매 [Q] 상점 나가기"
|
||||||
|
|
||||||
parts := []string{header, "", goldLine, items, "", menu}
|
parts := []string{header, "", goldLine, items, "", menu}
|
||||||
if shopMsg != "" {
|
if shopMsg != "" {
|
||||||
|
|||||||
@@ -33,22 +33,22 @@ func (s *StatsScreen) View(ctx *Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderStats(playerName string, stats store.PlayerStats, width, height int) string {
|
func renderStats(playerName string, stats store.PlayerStats, width, height int) string {
|
||||||
title := styleHeader.Render("── Player Statistics ──")
|
title := styleHeader.Render("── 플레이어 통계 ──")
|
||||||
|
|
||||||
var content string
|
var content string
|
||||||
content += stylePlayer.Render(fmt.Sprintf(" %s", playerName)) + "\n\n"
|
content += stylePlayer.Render(fmt.Sprintf(" %s", playerName)) + "\n\n"
|
||||||
content += fmt.Sprintf(" Total Runs: %s\n", styleGold.Render(fmt.Sprintf("%d", stats.TotalRuns)))
|
content += fmt.Sprintf(" 총 플레이: %s\n", styleGold.Render(fmt.Sprintf("%d", stats.TotalRuns)))
|
||||||
content += fmt.Sprintf(" Best Floor: %s\n", styleGold.Render(fmt.Sprintf("B%d", stats.BestFloor)))
|
content += fmt.Sprintf(" 최고 층: %s\n", styleGold.Render(fmt.Sprintf("B%d", stats.BestFloor)))
|
||||||
content += fmt.Sprintf(" Total Gold: %s\n", styleGold.Render(fmt.Sprintf("%d", stats.TotalGold)))
|
content += fmt.Sprintf(" 총 골드: %s\n", styleGold.Render(fmt.Sprintf("%d", stats.TotalGold)))
|
||||||
content += fmt.Sprintf(" Victories: %s\n", styleHeal.Render(fmt.Sprintf("%d", stats.Victories)))
|
content += fmt.Sprintf(" 승리 횟수: %s\n", styleHeal.Render(fmt.Sprintf("%d", stats.Victories)))
|
||||||
|
|
||||||
winRate := 0.0
|
winRate := 0.0
|
||||||
if stats.TotalRuns > 0 {
|
if stats.TotalRuns > 0 {
|
||||||
winRate = float64(stats.Victories) / float64(stats.TotalRuns) * 100
|
winRate = float64(stats.Victories) / float64(stats.TotalRuns) * 100
|
||||||
}
|
}
|
||||||
content += fmt.Sprintf(" Win Rate: %s\n", styleSystem.Render(fmt.Sprintf("%.1f%%", winRate)))
|
content += fmt.Sprintf(" 승률: %s\n", styleSystem.Render(fmt.Sprintf("%.1f%%", winRate)))
|
||||||
|
|
||||||
footer := styleSystem.Render("[S] Back")
|
footer := styleSystem.Render("[S] 뒤로")
|
||||||
|
|
||||||
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
||||||
lipgloss.JoinVertical(lipgloss.Center, title, "", content, "", footer))
|
lipgloss.JoinVertical(lipgloss.Center, title, "", content, "", footer))
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func (s *TitleScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
|
|||||||
gs := NewGameScreen()
|
gs := NewGameScreen()
|
||||||
gs.gameState = ctx.Session.GetState()
|
gs.gameState = ctx.Session.GetState()
|
||||||
ctx.Session.TouchActivity(ctx.Fingerprint)
|
ctx.Session.TouchActivity(ctx.Fingerprint)
|
||||||
ctx.Session.SendChat("System", ctx.PlayerName+" reconnected!")
|
ctx.Session.SendChat("System", ctx.PlayerName+" 재접속!")
|
||||||
return gs, gs.pollState()
|
return gs, gs.pollState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +101,7 @@ func renderTitle(width, height int) string {
|
|||||||
|
|
||||||
subtitle := lipgloss.NewStyle().
|
subtitle := lipgloss.NewStyle().
|
||||||
Foreground(colorGray).
|
Foreground(colorGray).
|
||||||
Render("⚔ A Cooperative Dungeon Crawler ⚔")
|
Render("⚔ 협동 던전 크롤러 ⚔")
|
||||||
|
|
||||||
server := lipgloss.NewStyle().
|
server := lipgloss.NewStyle().
|
||||||
Foreground(colorCyan).
|
Foreground(colorCyan).
|
||||||
@@ -110,7 +110,7 @@ func renderTitle(width, height int) string {
|
|||||||
menu := lipgloss.NewStyle().
|
menu := lipgloss.NewStyle().
|
||||||
Foreground(colorWhite).
|
Foreground(colorWhite).
|
||||||
Bold(true).
|
Bold(true).
|
||||||
Render("[Enter] Start [H] Help [S] Stats [A] Achievements [L] Leaderboard [C] Codex [Q] Quit")
|
Render("[Enter] 시작 [H] 도움말 [S] 통계 [A] 업적 [L] 리더보드 [C] 도감 [Q] 종료")
|
||||||
|
|
||||||
content := lipgloss.JoinVertical(lipgloss.Center,
|
content := lipgloss.JoinVertical(lipgloss.Center,
|
||||||
logo,
|
logo,
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func (s *WaitingScreen) View(ctx *Context) string {
|
|||||||
notReadyStyle := lipgloss.NewStyle().
|
notReadyStyle := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("240"))
|
Foreground(lipgloss.Color("240"))
|
||||||
|
|
||||||
header := headerStyle.Render(fmt.Sprintf("── Waiting Room [%s] ──", ctx.RoomCode))
|
header := headerStyle.Render(fmt.Sprintf("── 대기실 [%s] ──", ctx.RoomCode))
|
||||||
|
|
||||||
playerList := ""
|
playerList := ""
|
||||||
if ctx.Lobby != nil {
|
if ctx.Lobby != nil {
|
||||||
@@ -92,7 +92,7 @@ func (s *WaitingScreen) View(ctx *Context) string {
|
|||||||
for _, p := range room.Players {
|
for _, p := range room.Players {
|
||||||
status := notReadyStyle.Render("...")
|
status := notReadyStyle.Render("...")
|
||||||
if p.Ready {
|
if p.Ready {
|
||||||
status = readyStyle.Render("READY")
|
status = readyStyle.Render("준비 완료")
|
||||||
}
|
}
|
||||||
cls := p.Class
|
cls := p.Class
|
||||||
if cls == "" {
|
if cls == "" {
|
||||||
@@ -103,11 +103,11 @@ func (s *WaitingScreen) View(ctx *Context) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
menu := "[Enter] Ready"
|
menu := "[Enter] 준비"
|
||||||
if s.ready {
|
if s.ready {
|
||||||
menu = "Waiting for other players..."
|
menu = "다른 플레이어를 기다리는 중..."
|
||||||
}
|
}
|
||||||
menu += " [Esc] Leave"
|
menu += " [Esc] 나가기"
|
||||||
|
|
||||||
return lipgloss.JoinVertical(lipgloss.Left,
|
return lipgloss.JoinVertical(lipgloss.Left,
|
||||||
header,
|
header,
|
||||||
|
|||||||
Reference in New Issue
Block a user