aboutsummaryrefslogtreecommitdiff
path: root/internal/game/action_agility.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-19 13:28:06 -0400
committerhistoria <[not public]>2026-06-19 13:28:06 -0400
commit3a58125d14bb307f861b38c6c5b0a63babde7e08 (patch)
treebd89e866447d55e6dffd447d8ef5fabf9b8fbe9d /internal/game/action_agility.go
parent575a71dcfed3b9fa44af244836f638a23df05a9a (diff)
downloadthehouseoficarus-3a58125d14bb307f861b38c6c5b0a63babde7e08.tar.gz
feat: all skills kind of implemented, random prototype content added
Diffstat (limited to 'internal/game/action_agility.go')
-rw-r--r--internal/game/action_agility.go180
1 files changed, 180 insertions, 0 deletions
diff --git a/internal/game/action_agility.go b/internal/game/action_agility.go
new file mode 100644
index 0000000..089f026
--- /dev/null
+++ b/internal/game/action_agility.go
@@ -0,0 +1,180 @@
+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,
+ }
+
+ if g.Hub != nil {
+ for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
+ if other != sess && other.Player != nil {
+ other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\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 {
+ if newLevel := p.AddSkillXP(player.Agility, obstacleXP); newLevel > 0 {
+ sess.WriteLine(g.colorize(sess, "level_up",
+ fmt.Sprintf("*** You are now level %d agility! ***", newLevel)))
+ }
+ 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 {
+ if newLevel := p.AddSkillXP(player.Agility, completionXP); newLevel > 0 {
+ sess.WriteLine(g.colorize(sess, "level_up",
+ fmt.Sprintf("*** You are now level %d agility! ***", newLevel)))
+ }
+ 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", "You slip and fall!"))
+
+ p.HP -= damage
+ if p.HP < 1 {
+ p.HP = 1
+ }
+ sess.WriteLine(g.colorize(sess, "damage",
+ 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
+}