package game import ( "fmt" "math/rand" "time" "github.com/tolelom/catacombs/dungeon" "github.com/tolelom/catacombs/entity" ) func (s *GameSession) EnterRoom(roomIdx int) { s.mu.Lock() defer s.mu.Unlock() now := time.Now() for _, p := range s.state.Players { if p.Fingerprint != "" { s.lastActivity[p.Fingerprint] = now } } s.state.Floor.CurrentRoom = roomIdx dungeon.UpdateVisibility(s.state.Floor) room := s.state.Floor.Rooms[roomIdx] if room.Cleared { return } switch room.Type { case dungeon.RoomCombat: s.spawnMonsters() s.state.Phase = PhaseCombat s.state.CombatTurn = 0 s.signalCombat() case dungeon.RoomBoss: s.spawnBoss() s.state.Phase = PhaseCombat s.state.CombatTurn = 0 s.signalCombat() case dungeon.RoomShop: s.generateShopItems() s.state.Phase = PhaseShop case dungeon.RoomTreasure: s.grantTreasure() room.Cleared = true case dungeon.RoomEvent: s.triggerEvent() room.Cleared = true case dungeon.RoomEmpty: room.Cleared = true } } func (s *GameSession) spawnMonsters() { count := 1 + rand.Intn(5) floor := s.state.FloorNum s.state.Monsters = make([]*entity.Monster, count) type floorRange struct { mt entity.MonsterType minFloor int maxFloor int } ranges := []floorRange{ {entity.MonsterSlime, 1, 5}, {entity.MonsterSkeleton, 3, 10}, {entity.MonsterOrc, 6, 14}, {entity.MonsterDarkKnight, 12, 20}, } var valid []entity.MonsterType for _, r := range ranges { if floor >= r.minFloor && floor <= r.maxFloor { valid = append(valid, r.mt) } } if len(valid) == 0 { valid = []entity.MonsterType{entity.MonsterSlime} } for i := 0; i < count; i++ { mt := valid[rand.Intn(len(valid))] m := entity.NewMonster(mt, floor, s.cfg.Combat.MonsterScaling) if s.state.SoloMode { m.HP = int(float64(m.HP) * s.cfg.Combat.SoloHPReduction) if m.HP < 1 { m.HP = 1 } m.MaxHP = m.HP m.DEF = int(float64(m.DEF) * s.cfg.Combat.SoloHPReduction) } s.state.Monsters[i] = m } // Reset skill uses for all players at combat start for _, p := range s.state.Players { p.SkillUses = s.cfg.Game.SkillUses } } func (s *GameSession) spawnBoss() { var mt entity.MonsterType switch s.state.FloorNum { case 5: mt = entity.MonsterBoss5 case 10: mt = entity.MonsterBoss10 case 15: mt = entity.MonsterBoss15 case 20: mt = entity.MonsterBoss20 default: mt = entity.MonsterBoss5 } boss := entity.NewMonster(mt, s.state.FloorNum, s.cfg.Combat.MonsterScaling) switch mt { case entity.MonsterBoss5: boss.Pattern = entity.PatternAoE case entity.MonsterBoss10: boss.Pattern = entity.PatternPoison case entity.MonsterBoss15: boss.Pattern = entity.PatternBurn case entity.MonsterBoss20: boss.Pattern = entity.PatternHeal } if s.state.SoloMode { boss.HP = int(float64(boss.HP) * s.cfg.Combat.SoloHPReduction) boss.MaxHP = boss.HP boss.DEF = int(float64(boss.DEF) * s.cfg.Combat.SoloHPReduction) } s.state.Monsters = []*entity.Monster{boss} // Reset skill uses for all players at combat start for _, p := range s.state.Players { p.SkillUses = s.cfg.Game.SkillUses } } 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)) continue } if rand.Float64() < 0.5 { bonus := 3 + rand.Intn(6) + floor/3 item := entity.Item{ 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)) } 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)) } } } func (s *GameSession) generateShopItems() { floor := s.state.FloorNum // Weapon bonus scales: base 3-8 + floor/3 weaponBonus := 3 + rand.Intn(6) + floor/3 // Armor bonus scales: base 2-5 + floor/4 armorBonus := 2 + rand.Intn(4) + floor/4 // Prices scale with power weaponPrice := 40 + weaponBonus*5 armorPrice := 30 + armorBonus*5 // Potion heals more on higher floors potionHeal := 30 + floor potionPrice := 20 + floor/2 s.state.ShopItems = []entity.Item{ {Name: "HP Potion", Type: entity.ItemConsumable, Bonus: potionHeal, Price: potionPrice}, {Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: weaponBonus, Price: weaponPrice}, {Name: armorName(floor), Type: entity.ItemArmor, Bonus: armorBonus, Price: armorPrice}, } } func weaponName(floor int) string { switch { case floor >= 15: return "Mythril Blade" case floor >= 10: return "Steel Sword" case floor >= 5: return "Bronze Sword" default: return "Iron Sword" } } func armorName(floor int) string { switch { case floor >= 15: return "Mythril Shield" case floor >= 10: return "Steel Shield" case floor >= 5: return "Bronze Shield" default: return "Iron Shield" } } func (s *GameSession) triggerEvent() { for _, p := range s.state.Players { if p.IsDead() { continue } if rand.Float64() < 0.5 { baseDmg := 10 + s.state.FloorNum dmg := baseDmg + rand.Intn(baseDmg/2+1) p.TakeDamage(dmg) s.addLog(fmt.Sprintf("Trap! %s takes %d damage", p.Name, dmg)) } else { baseHeal := 15 + s.state.FloorNum heal := baseHeal + rand.Intn(baseHeal/2+1) before := p.HP p.Heal(heal) s.addLog(fmt.Sprintf("Blessing! %s heals %d HP", p.Name, p.HP-before)) } } }