package game import ( "fmt" "math/rand" "thehouseoficarus/internal/action" "thehouseoficarus/internal/engine" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" ) func (g *Game) startObstacle(sess *net.Session, p *player.Player, info *ObstacleInfo) { failChance := g.calcFailChance(p, info) msgs := make([]any, len(info.Messages)) for i, m := range info.Messages { msgs[i] = m } gerund := info.Verb + "ing" if g, ok := verbGerund[info.Verb]; ok { gerund = g } p.Action = &action.Action{ Type: "obstacle", TargetID: info.CourseID, TargetName: info.CourseName, WaitLeft: 0, Data: map[string]any{ "course_id": info.CourseID, "phase": 0, "obstacle_index": info.ObstacleIndex, "total_obstacles": info.TotalObstacles, "next_room": info.NextRoom, "start_room": info.StartRoom, "obstacle_xp": info.ObstacleXP, "completion_xp": info.CompletionXP, "fail_chance": failChance, "fail_damage_min": info.FailDamage[0], "fail_damage_max": info.FailDamage[1], "messages": msgs, "ticks_per_phase": info.TicksPerPhase, "required_level": info.RequiredLevel, }, } p.ActionState = &ActionState{ Type: ActionTraversing, Verb: gerund, TargetName: info.CourseName, } g.broadcastAction(sess, "\n%s starts %s on the %s.", p.Name, gerund, info.CourseName) } func (g *Game) advanceObstacle(sess *net.Session, p *player.Player) { data := p.Action.Data phase := data["phase"].(int) messages := data["messages"].([]any) ticksPerPhase := data["ticks_per_phase"].(float64) if phase >= len(messages) { g.cancelAction(p) return } msg := messages[phase].(string) sess.WriteLine(g.colorize(sess, "broadcast", msg)) if phase == 1 { failChance := data["fail_chance"].(float64) if rand.Float64() < failChance { g.obstacleFail(sess, p, data) return } } nextPhase := phase + 1 if nextPhase >= len(messages) { obstacleXP := data["obstacle_xp"].(int) completionXP := data["completion_xp"].(int) nextRoom := data["next_room"].(int) startRoom := data["start_room"].(int) obstacleIndex := data["obstacle_index"].(int) totalObstacles := data["total_obstacles"].(int) courseID := data["course_id"].(string) if obstacleXP > 0 { g.awardSkillXP(sess, p, player.Agility, obstacleXP) if p.OptionBool("xp_drops") { sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("(+%dxp %s)", obstacleXP, player.SkillAbbr[player.Agility]))) } } isLastObstacle := obstacleIndex == totalObstacles-1 if isLastObstacle { if completionXP > 0 { g.awardSkillXP(sess, p, player.Agility, completionXP) if p.OptionBool("xp_drops") { sess.WriteLine(g.colorize(sess, "xp", fmt.Sprintf("(+%dxp %s course bonus)", completionXP, player.SkillAbbr[player.Agility]))) } } lapKey := "agility_laps_" + courseID laps := getPlayerFlagInt(p, lapKey) + 1 setPlayerFlag(p, lapKey, laps) courseName := p.Action.TargetName sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("Course complete! %s lap %d finished.", courseName, laps))) g.cancelAction(p) g.teleportPlayer(sess, p, startRoom) } else { g.cancelAction(p) g.teleportPlayer(sess, p, nextRoom) } g.AccountStore.SaveCharacter(p) return } data["phase"] = nextPhase p.Action.WaitLeft = engine.ToTicks(ticksPerPhase) } func (g *Game) obstacleFail(sess *net.Session, p *player.Player, data map[string]any) { startRoom := data["start_room"].(int) failMin := data["fail_damage_min"].(int) failMax := data["fail_damage_max"].(int) damage := failMin if failMax > failMin { damage = failMin + rand.Intn(failMax-failMin+1) } sess.WriteLine(g.colorize(sess, "damage_taken", "You slip and fall!")) p.HP -= damage if p.HP < 1 { p.HP = 1 } sess.WriteLine(g.colorize(sess, "damage_taken", fmt.Sprintf("You take %d damage. HP: %d/%d", damage, p.HP, p.MaxHP()))) g.AccountStore.SaveCharacter(p) g.cancelAction(p) g.teleportPlayer(sess, p, startRoom) } func (g *Game) calcFailChance(p *player.Player, info *ObstacleInfo) float64 { level := p.Level(player.Agility) required := info.RequiredLevel chance := 0.30 - float64(level-required)*0.01 if chance < 0.05 { chance = 0.05 } if chance > 0.60 { chance = 0.60 } return chance }