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:
@@ -1,11 +1,11 @@
|
||||
package game
|
||||
|
||||
var emotes = map[string]string{
|
||||
"/hi": "👋 waves hello!",
|
||||
"/gg": "🎉 says GG!",
|
||||
"/go": "⚔️ says Let's go!",
|
||||
"/wait": "✋ says Wait!",
|
||||
"/help": "🆘 calls for help!",
|
||||
"/hi": "👋 인사합니다!",
|
||||
"/gg": "🎉 GG!",
|
||||
"/go": "⚔️ 가자!",
|
||||
"/wait": "✋ 기다려!",
|
||||
"/help": "🆘 도움 요청!",
|
||||
}
|
||||
|
||||
func ParseEmote(input string) (string, bool) {
|
||||
|
||||
@@ -8,11 +8,11 @@ func TestParseEmote(t *testing.T) {
|
||||
isEmote bool
|
||||
expected string
|
||||
}{
|
||||
{"/hi", true, "👋 waves hello!"},
|
||||
{"/gg", true, "🎉 says GG!"},
|
||||
{"/go", true, "⚔️ says Let's go!"},
|
||||
{"/wait", true, "✋ says Wait!"},
|
||||
{"/help", true, "🆘 calls for help!"},
|
||||
{"/hi", true, "👋 인사합니다!"},
|
||||
{"/gg", true, "🎉 GG!"},
|
||||
{"/go", true, "⚔️ 가자!"},
|
||||
{"/wait", true, "✋ 기다려!"},
|
||||
{"/help", true, "🆘 도움 요청!"},
|
||||
{"/unknown", false, ""},
|
||||
{"hello", false, ""},
|
||||
{"", false, ""},
|
||||
|
||||
@@ -41,7 +41,7 @@ func (s *GameSession) EnterRoom(roomIdx int) {
|
||||
s.signalCombat()
|
||||
case dungeon.RoomShop:
|
||||
if s.hasMutation("no_shop") {
|
||||
s.addLog("The shop is closed! (Weekly mutation)")
|
||||
s.addLog("상점이 닫혔습니다! (주간 변이)")
|
||||
room.Cleared = true
|
||||
return
|
||||
}
|
||||
@@ -169,7 +169,7 @@ func (s *GameSession) grantTreasure() {
|
||||
floor := s.state.FloorNum
|
||||
for _, p := range s.state.Players {
|
||||
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
|
||||
}
|
||||
if rand.Float64() < 0.5 {
|
||||
@@ -178,14 +178,14 @@ func (s *GameSession) grantTreasure() {
|
||||
Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: bonus,
|
||||
}
|
||||
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 {
|
||||
bonus := 2 + rand.Intn(4) + floor/4
|
||||
item := entity.Item{
|
||||
Name: armorName(floor), Type: entity.ItemArmor, Bonus: bonus,
|
||||
}
|
||||
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() {
|
||||
event := PickRandomEvent()
|
||||
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
|
||||
choice := event.Choices[rand.Intn(len(event.Choices))]
|
||||
@@ -267,10 +267,10 @@ func (s *GameSession) triggerEvent() {
|
||||
if outcome.HPChange > 0 {
|
||||
before := target.HP
|
||||
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 {
|
||||
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 {
|
||||
@@ -279,9 +279,9 @@ func (s *GameSession) triggerEvent() {
|
||||
target.Gold = 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 {
|
||||
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
|
||||
item := entity.Item{Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: bonus}
|
||||
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 {
|
||||
bonus := 2 + rand.Intn(4) + floor/4
|
||||
item := entity.Item{Name: armorName(floor), Type: entity.ItemArmor, Bonus: bonus}
|
||||
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 {
|
||||
s.addLog(fmt.Sprintf(" %s's inventory is full!", target.Name))
|
||||
s.addLog(fmt.Sprintf(" %s의 인벤토리가 가득 찼습니다!", target.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *GameSession) grantSecretTreasure() {
|
||||
s.addLog("You discovered a secret room filled with treasure!")
|
||||
s.addLog("보물로 가득 찬 비밀의 방을 발견했습니다!")
|
||||
floor := s.state.FloorNum
|
||||
// Double treasure: grant two items per player
|
||||
for _, p := range s.state.Players {
|
||||
for i := 0; i < 2; i++ {
|
||||
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
|
||||
}
|
||||
if rand.Float64() < 0.5 {
|
||||
@@ -321,14 +321,14 @@ func (s *GameSession) grantSecretTreasure() {
|
||||
Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: bonus,
|
||||
}
|
||||
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 {
|
||||
bonus := 2 + rand.Intn(4) + floor/4
|
||||
item := entity.Item{
|
||||
Name: armorName(floor), Type: entity.ItemArmor, Bonus: bonus,
|
||||
}
|
||||
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)
|
||||
}
|
||||
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
|
||||
for _, p := range s.state.Players {
|
||||
|
||||
@@ -117,10 +117,10 @@ func (l *Lobby) InvitePlayer(roomCode, fingerprint string) error {
|
||||
defer l.mu.Unlock()
|
||||
p, ok := l.online[fingerprint]
|
||||
if !ok {
|
||||
return fmt.Errorf("player not online")
|
||||
return fmt.Errorf("플레이어가 온라인이 아닙니다")
|
||||
}
|
||||
if p.InRoom != "" {
|
||||
return fmt.Errorf("player already in a room")
|
||||
return fmt.Errorf("플레이어가 이미 방에 있습니다")
|
||||
}
|
||||
// Store the invite as a pending field
|
||||
p.InRoom = "invited:" + roomCode
|
||||
@@ -148,13 +148,13 @@ func (l *Lobby) JoinRoom(code, playerName, fingerprint string) error {
|
||||
defer l.mu.Unlock()
|
||||
room, ok := l.rooms[code]
|
||||
if !ok {
|
||||
return fmt.Errorf("room %s not found", code)
|
||||
return fmt.Errorf("방 %s을(를) 찾을 수 없습니다", code)
|
||||
}
|
||||
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 {
|
||||
return fmt.Errorf("room %s already in progress", code)
|
||||
return fmt.Errorf("방 %s이(가) 이미 진행 중입니다", code)
|
||||
}
|
||||
room.Players = append(room.Players, LobbyPlayer{Name: playerName, Fingerprint: fingerprint})
|
||||
slog.Info("player joined", "room", code, "player", playerName)
|
||||
|
||||
@@ -19,15 +19,15 @@ type Mutation struct {
|
||||
|
||||
// Mutations is the list of all available mutations.
|
||||
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 }},
|
||||
{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) }},
|
||||
{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
|
||||
{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
|
||||
{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
|
||||
}
|
||||
|
||||
|
||||
@@ -28,211 +28,211 @@ func GetRandomEvents() []RandomEvent {
|
||||
return []RandomEvent{
|
||||
{
|
||||
Name: "altar",
|
||||
Description: "You discover an ancient altar glowing with strange energy.",
|
||||
Description: "이상한 에너지로 빛나는 고대 제단을 발견했습니다.",
|
||||
Choices: []EventChoice{
|
||||
{
|
||||
Label: "Pray at the altar",
|
||||
Label: "제단에서 기도하기",
|
||||
Resolve: func(floor int) EventOutcome {
|
||||
if rand.Float64() < 0.6 {
|
||||
heal := 15 + floor*2
|
||||
return EventOutcome{HPChange: heal, Description: "The altar blesses you with healing light."}
|
||||
return EventOutcome{HPChange: heal, Description: "제단이 치유의 빛으로 축복합니다."}
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
return EventOutcome{Description: "You leave the altar undisturbed."}
|
||||
return EventOutcome{Description: "제단을 건드리지 않고 떠납니다."}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "fountain",
|
||||
Description: "A shimmering fountain bubbles in the center of the room.",
|
||||
Description: "방 중앙에서 빛나는 분수가 솟아오릅니다.",
|
||||
Choices: []EventChoice{
|
||||
{
|
||||
Label: "Drink from the fountain",
|
||||
Label: "분수의 물 마시기",
|
||||
Resolve: func(floor int) EventOutcome {
|
||||
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 {
|
||||
if rand.Float64() < 0.5 {
|
||||
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",
|
||||
Description: "A hooded merchant appears from the shadows.",
|
||||
Description: "두건을 쓴 상인이 어둠 속에서 나타납니다.",
|
||||
Choices: []EventChoice{
|
||||
{
|
||||
Label: "Trade gold for healing",
|
||||
Label: "골드로 치료 거래",
|
||||
Resolve: func(floor int) EventOutcome {
|
||||
cost := 15 + floor
|
||||
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 {
|
||||
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 {
|
||||
return EventOutcome{Description: "The merchant vanishes into the shadows."}
|
||||
return EventOutcome{Description: "상인이 어둠 속으로 사라집니다."}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "trap_room",
|
||||
Description: "The floor is covered with suspicious pressure plates.",
|
||||
Description: "바닥이 수상한 압력판으로 덮여 있습니다.",
|
||||
Choices: []EventChoice{
|
||||
{
|
||||
Label: "Carefully navigate",
|
||||
Label: "조심히 지나가기",
|
||||
Resolve: func(floor int) EventOutcome {
|
||||
if rand.Float64() < 0.5 {
|
||||
return EventOutcome{Description: "You skillfully avoid all the traps!"}
|
||||
return EventOutcome{Description: "능숙하게 모든 함정을 피했습니다!"}
|
||||
}
|
||||
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 {
|
||||
dmg := 5 + 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",
|
||||
Description: "A glowing shrine hums with divine power.",
|
||||
Description: "신성한 힘으로 울리는 빛나는 성소가 있습니다.",
|
||||
Choices: []EventChoice{
|
||||
{
|
||||
Label: "Kneel and pray",
|
||||
Label: "무릎 꿇고 기도하기",
|
||||
Resolve: func(floor int) EventOutcome {
|
||||
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 {
|
||||
gold := 20 + floor*3
|
||||
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",
|
||||
Description: "An ornate chest sits in the corner of the room.",
|
||||
Description: "방 구석에 화려한 상자가 놓여 있습니다.",
|
||||
Choices: []EventChoice{
|
||||
{
|
||||
Label: "Open carefully",
|
||||
Label: "조심히 열기",
|
||||
Resolve: func(floor int) EventOutcome {
|
||||
if rand.Float64() < 0.7 {
|
||||
gold := 15 + floor*2
|
||||
return EventOutcome{GoldChange: gold, Description: "The chest contains a pile of gold!"}
|
||||
return EventOutcome{GoldChange: gold, Description: "상자 안에 골드 더미가 있습니다!"}
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
return EventOutcome{Description: "Better safe than sorry."}
|
||||
return EventOutcome{Description: "안전한 게 최고입니다."}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ghost",
|
||||
Description: "A spectral figure materializes before you.",
|
||||
Description: "유령 같은 형체가 눈앞에 나타납니다.",
|
||||
Choices: []EventChoice{
|
||||
{
|
||||
Label: "Speak with the ghost",
|
||||
Label: "유령과 대화하기",
|
||||
Resolve: func(floor int) EventOutcome {
|
||||
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 {
|
||||
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
|
||||
return EventOutcome{HPChange: -dmg, Description: "The ghost retaliates with ghostly fury!"}
|
||||
return EventOutcome{HPChange: -dmg, Description: "유령이 분노하여 반격합니다!"}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "mushroom",
|
||||
Description: "Strange glowing mushrooms grow in clusters here.",
|
||||
Description: "이상하게 빛나는 버섯들이 무리 지어 자라고 있습니다.",
|
||||
Choices: []EventChoice{
|
||||
{
|
||||
Label: "Eat a mushroom",
|
||||
Label: "버섯 먹기",
|
||||
Resolve: func(floor int) EventOutcome {
|
||||
r := rand.Float64()
|
||||
if r < 0.33 {
|
||||
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 {
|
||||
dmg := 10 + floor
|
||||
return EventOutcome{HPChange: -dmg, Description: "The mushroom was poisonous!"}
|
||||
return EventOutcome{HPChange: -dmg, Description: "독버섯이었습니다!"}
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
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 now.Sub(last) > 60*time.Second {
|
||||
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
|
||||
continue
|
||||
}
|
||||
@@ -335,15 +335,15 @@ func (s *GameSession) SubmitAction(playerID string, action PlayerAction) {
|
||||
desc := ""
|
||||
switch action.Type {
|
||||
case ActionAttack:
|
||||
desc = "Attacking"
|
||||
desc = "공격"
|
||||
case ActionSkill:
|
||||
desc = "Using Skill"
|
||||
desc = "스킬 사용"
|
||||
case ActionItem:
|
||||
desc = "Using Item"
|
||||
desc = "아이템 사용"
|
||||
case ActionFlee:
|
||||
desc = "Fleeing"
|
||||
desc = "도주"
|
||||
case ActionWait:
|
||||
desc = "Defending"
|
||||
desc = "방어"
|
||||
}
|
||||
if s.state.SubmittedActions == nil {
|
||||
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 {
|
||||
if p.Fingerprint == fingerprint {
|
||||
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 fmt.Errorf("player not found")
|
||||
return fmt.Errorf("플레이어를 찾을 수 없습니다")
|
||||
}
|
||||
|
||||
// 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))
|
||||
if bonus > 0 {
|
||||
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() {
|
||||
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
|
||||
if frozenPlayers[p.Fingerprint] {
|
||||
s.addLog(fmt.Sprintf("%s is frozen and cannot act!", p.Name))
|
||||
s.addLog(fmt.Sprintf("%s 동결되어 행동할 수 없습니다!", p.Name))
|
||||
continue
|
||||
}
|
||||
action, ok := s.actions[p.Fingerprint]
|
||||
@@ -146,7 +146,7 @@ func (s *GameSession) resolvePlayerActions() {
|
||||
intentOwners = append(intentOwners, p.Name)
|
||||
case ActionSkill:
|
||||
if p.SkillUses <= 0 {
|
||||
s.addLog(fmt.Sprintf("%s has no skill uses left!", p.Name))
|
||||
s.addLog(fmt.Sprintf("%s 스킬 사용 횟수가 없습니다!", p.Name))
|
||||
break
|
||||
}
|
||||
p.SkillUses--
|
||||
@@ -158,7 +158,7 @@ func (s *GameSession) resolvePlayerActions() {
|
||||
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:
|
||||
skillPower := 0
|
||||
if p.Skills != nil {
|
||||
@@ -197,13 +197,13 @@ func (s *GameSession) resolvePlayerActions() {
|
||||
}
|
||||
before := target.HP
|
||||
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:
|
||||
currentRoom := s.state.Floor.Rooms[s.state.Floor.CurrentRoom]
|
||||
for _, neighborIdx := range currentRoom.Neighbors {
|
||||
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:
|
||||
found := false
|
||||
@@ -216,17 +216,17 @@ func (s *GameSession) resolvePlayerActions() {
|
||||
}
|
||||
p.Heal(healAmt)
|
||||
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
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
s.addLog(fmt.Sprintf("%s has no items to use!", p.Name))
|
||||
s.addLog(fmt.Sprintf("%s 사용할 아이템이 없습니다!", p.Name))
|
||||
}
|
||||
case ActionFlee:
|
||||
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
|
||||
if s.state.SoloMode {
|
||||
s.state.Phase = PhaseExploring
|
||||
@@ -234,10 +234,10 @@ func (s *GameSession) resolvePlayerActions() {
|
||||
}
|
||||
p.Fled = true
|
||||
} else {
|
||||
s.addLog(fmt.Sprintf("%s failed to flee!", p.Name))
|
||||
s.addLog(fmt.Sprintf("%s 도주에 실패했습니다!", p.Name))
|
||||
}
|
||||
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 {
|
||||
s.state.Phase = PhaseExploring
|
||||
s.state.Floor.Rooms[s.state.Floor.CurrentRoom].Cleared = true
|
||||
s.addLog("All players fled!")
|
||||
s.addLog("모든 플레이어가 도주했습니다!")
|
||||
for _, p := range s.state.Players {
|
||||
p.Fled = false
|
||||
}
|
||||
@@ -307,16 +307,16 @@ func (s *GameSession) resolvePlayerActions() {
|
||||
if r.IsAoE {
|
||||
coopStr := ""
|
||||
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) {
|
||||
target := s.state.Monsters[r.TargetIdx]
|
||||
coopStr := ""
|
||||
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
|
||||
if r.Damage > 0 {
|
||||
@@ -326,7 +326,7 @@ func (s *GameSession) resolvePlayerActions() {
|
||||
heal := r.Damage * rel.Value / 100
|
||||
if heal > 0 {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
s.addLog(fmt.Sprintf("%s defeated! +%d gold", m.Name, goldReward))
|
||||
s.addLog(fmt.Sprintf("%s 처치! +%d 골드", m.Name, goldReward))
|
||||
if m.IsBoss {
|
||||
s.state.BossKilled = true
|
||||
s.grantBossRelic()
|
||||
@@ -385,7 +385,7 @@ func (s *GameSession) resolvePlayerActions() {
|
||||
// Check if combat is over
|
||||
if len(s.state.Monsters) == 0 {
|
||||
s.state.Floor.Rooms[s.state.Floor.CurrentRoom].Cleared = true
|
||||
s.addLog("Room cleared!")
|
||||
s.addLog("방 클리어!")
|
||||
for _, p := range s.state.Players {
|
||||
p.Fled = false
|
||||
}
|
||||
@@ -402,7 +402,7 @@ func (s *GameSession) advanceFloor() {
|
||||
s.state.Phase = PhaseResult
|
||||
s.state.Victory = true
|
||||
s.state.GameOver = true
|
||||
s.addLog("You conquered the Catacombs!")
|
||||
s.addLog("카타콤을 정복했습니다!")
|
||||
return
|
||||
}
|
||||
// Grant 1 skill point per floor clear
|
||||
@@ -421,11 +421,11 @@ func (s *GameSession) advanceFloor() {
|
||||
}
|
||||
s.state.Phase = PhaseExploring
|
||||
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 {
|
||||
if p.IsDead() {
|
||||
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
|
||||
}
|
||||
@@ -445,7 +445,7 @@ func (s *GameSession) grantBossRelic() {
|
||||
if !p.IsOut() {
|
||||
r := relics[rand.Intn(len(relics))]
|
||||
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() {
|
||||
dmg := combat.CalcDamage(m.ATK, p.EffectiveDEF(), 0.5)
|
||||
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() {
|
||||
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 {
|
||||
if !p.IsOut() {
|
||||
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:
|
||||
for _, p := range s.state.Players {
|
||||
if !p.IsOut() {
|
||||
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:
|
||||
for _, p := range s.state.Players {
|
||||
if !p.IsOut() {
|
||||
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:
|
||||
@@ -500,7 +500,7 @@ func (s *GameSession) resolveMonsterActions() {
|
||||
if 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 {
|
||||
@@ -509,20 +509,20 @@ func (s *GameSession) resolveMonsterActions() {
|
||||
if !p.IsOut() {
|
||||
dmg := combat.CalcDamage(m.ATK, p.EffectiveDEF(), 1.0)
|
||||
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 {
|
||||
def := entity.ElitePrefixDefs[m.ElitePrefix]
|
||||
if def.OnHit >= 0 {
|
||||
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 {
|
||||
heal := dmg / 4
|
||||
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() {
|
||||
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 {
|
||||
s.state.Phase = PhaseResult
|
||||
s.state.GameOver = true
|
||||
s.addLog("Party wiped!")
|
||||
s.addLog("파티가 전멸했습니다!")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user