aboutsummaryrefslogtreecommitdiff
path: root/internal/game/act_agility.go
blob: d66b9c567179da9ec7e84447f7724fed003a3b3f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package game

import (
	"fmt"
	"math/rand"

	"thehouseoficarus/internal/behavior"
	"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 = &behavior.Action{
		Type:       behavior.TypeObstacle,
		TargetID:   info.CourseID,
		TargetName: info.CourseName,
		WaitLeft:   0,
		Data: &behavior.ObstacleData{
			CourseID:       info.CourseID,
			Phase:          0,
			ObstacleIndex:  info.ObstacleIndex,
			TotalObstacles: info.TotalObstacles,
			NextRoom:       info.NextRoom,
			StartRoom:      info.StartRoom,
			ObstacleXP:     info.ObstacleXP,
			CompletionXP:   info.CompletionXP,
			FailChance:     failChance,
			FailDamageMin:  info.FailDamage[0],
			FailDamageMax:  info.FailDamage[1],
			Messages:       msgs,
			TicksPerPhase:  info.TicksPerPhase,
			RequiredLevel:  info.RequiredLevel,
		},
	}

	g.broadcastAction(sess, "%s starts %s on the %s.", p.Name, gerund, info.CourseName)
}

func (g *Game) advanceObstacle(sess *net.Session, p *player.Player) {
	d := p.Action.Data.(*behavior.ObstacleData)
	phase := d.Phase
	messages := d.Messages
	ticksPerPhase := d.TicksPerPhase

	if phase >= len(messages) {
		g.cancelAction(p)
		return
	}

	msg := messages[phase].(string)
	sess.WriteLine(g.colorize(sess, "broadcast", msg))

	if phase == 1 {
		failChance := d.FailChance
		if rand.Float64() < failChance {
			g.obstacleFail(sess, p, d)
			return
		}
	}

	nextPhase := phase + 1

	if nextPhase >= len(messages) {
		obstacleXP := d.ObstacleXP
		completionXP := d.CompletionXP
		nextRoom := d.NextRoom
		startRoom := d.StartRoom
		obstacleIndex := d.ObstacleIndex
		totalObstacles := d.TotalObstacles
		courseID := d.CourseID

		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
			g.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
	}

	d.Phase = nextPhase
	p.Action.WaitLeft = engine.ToTicks(ticksPerPhase)
}

func (g *Game) obstacleFail(sess *net.Session, p *player.Player, d *behavior.ObstacleData) {
	startRoom := d.StartRoom
	failMin := d.FailDamageMin
	failMax := d.FailDamageMax

	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
}