Skip to content

Commit 50b76d8

Browse files
committed
init
1 parent 94fd807 commit 50b76d8

File tree

12 files changed

+429
-0
lines changed

12 files changed

+429
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.exe
2+
*-linux

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
build: rotten-blood-boss-timer ebb-and-flow-timer
3+
4+
rotten-blood-boss-timer:
5+
GOARCH=amd64 GOOS=linux go build -o rotten-blood-boss-timer-linux -ldflags="-X 'main.WindowTitle=Rotten Blood Boss Timer' -X main.FirstAlarm=5s -X main.SecondAlarm=78s -X main.Interval=90s" ./cmd/tibia-timer
6+
GOARCH=amd64 GOOS=windows go build -o rotten-blood-boss-timer.exe -ldflags="-X 'main.WindowTitle=Rotten Blood Boss Timer' -X main.FirstAlarm=5s -X main.SecondAlarm=78s -X main.Interval=90s" ./cmd/tibia-timer
7+
8+
ebb-and-flow-timer:
9+
GOARCH=amd64 GOOS=linux go build -o ebb-and-flow-timer-linux -ldflags="-X 'main.WindowTitle=Ebb and Flow Timer' -X main.FirstAlarm=15s -X main.SecondAlarm=0 -X main.Interval=120s" ./cmd/tibia-timer
10+
GOARCH=amd64 GOOS=windows go build -o ebb-and-flow-timer.exe -ldflags="-X 'main.WindowTitle=Ebb and Flow Timer' -X main.FirstAlarm=15s -X main.SecondAlarm=0 -X main.Interval=120s" ./cmd/tibia-timer

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Tibia-timer
2+
3+
All sound files should be free of use, create an issue and I will update to some other effect.

cmd/tibia-timer/alert.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/hajimehoshi/ebiten/v2/audio"
8+
"github.com/hajimehoshi/ebiten/v2/audio/mp3"
9+
)
10+
11+
var (
12+
audioContext = audio.NewContext(32000)
13+
)
14+
15+
type Alert struct {
16+
when time.Duration
17+
18+
mute bool
19+
20+
audioPlayer *audio.Player
21+
}
22+
23+
func (a *Alert) SetVolume(vol float64) {
24+
a.audioPlayer.SetVolume(vol)
25+
}
26+
27+
func (a *Alert) SetMute(m bool) {
28+
a.mute = m
29+
}
30+
31+
func (a *Alert) Update(duration time.Duration) {
32+
if a.when == 0 {
33+
return
34+
}
35+
36+
if a.mute {
37+
return
38+
}
39+
40+
if duration == a.when && !a.audioPlayer.IsPlaying() {
41+
a.audioPlayer.Play()
42+
a.audioPlayer.Rewind()
43+
}
44+
}
45+
46+
func NewAlert(when time.Duration, file string) (*Alert, error) {
47+
audioPlayer, err := loadAudioPlayer(file)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
return &Alert{
53+
when: when,
54+
audioPlayer: audioPlayer,
55+
}, nil
56+
}
57+
58+
func loadAudioPlayer(file string) (*audio.Player, error) {
59+
audioFile, err := fs.Open(file)
60+
if err != nil {
61+
return nil, fmt.Errorf("failed to open file: %w", err)
62+
}
63+
stream, err := mp3.DecodeWithoutResampling(audioFile)
64+
if err != nil {
65+
return nil, fmt.Errorf("failed to decode audio file: %w", err)
66+
}
67+
68+
audioPlayer, err := audioContext.NewPlayer(stream)
69+
if err != nil {
70+
return nil, fmt.Errorf("failed to create audio player: %w", err)
71+
}
72+
return audioPlayer, nil
73+
}

cmd/tibia-timer/config.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
var (
9+
FirstAlarm = ""
10+
SecondAlarm = ""
11+
Interval = ""
12+
WindowTitle = ""
13+
)
14+
15+
type Config struct {
16+
FirstAlarm time.Duration
17+
SecondAlarm time.Duration
18+
Interval time.Duration
19+
Volume float64
20+
}
21+
22+
func (c *Config) Validate() error {
23+
if c.FirstAlarm == 0 {
24+
return fmt.Errorf("missing first alarm")
25+
}
26+
27+
if c.Interval == 0 {
28+
return fmt.Errorf("missing interval")
29+
}
30+
31+
return nil
32+
}
33+
34+
func MustParseDuration(s string) time.Duration {
35+
d, err := time.ParseDuration(s)
36+
if err != nil {
37+
panic(fmt.Errorf("must parse duration: %w", err))
38+
}
39+
return d
40+
}

cmd/tibia-timer/files/goat.mp3

32.4 KB
Binary file not shown.

cmd/tibia-timer/files/quack.mp3

60.9 KB
Binary file not shown.

cmd/tibia-timer/files/tahoma.ttf

259 KB
Binary file not shown.

cmd/tibia-timer/main.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package main
2+
3+
import (
4+
"embed"
5+
"fmt"
6+
"image/color"
7+
"log"
8+
"time"
9+
10+
"github.com/hajimehoshi/ebiten/v2"
11+
"github.com/hajimehoshi/ebiten/v2/audio"
12+
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
13+
"github.com/hajimehoshi/ebiten/v2/inpututil"
14+
"github.com/hajimehoshi/ebiten/v2/text"
15+
"golang.org/x/image/font"
16+
"golang.org/x/image/font/opentype"
17+
)
18+
19+
// Controller or System
20+
type Controller interface {
21+
Update(duration time.Duration)
22+
Draw(screen *ebiten.Image)
23+
}
24+
25+
type Component interface {
26+
Update(duration time.Duration)
27+
}
28+
29+
type Screen struct {
30+
time time.Time
31+
startTime time.Time
32+
33+
controllers []Controller
34+
35+
config *Config
36+
37+
audioPlayer *audio.Player
38+
font font.Face
39+
}
40+
41+
var (
42+
//go:embed files
43+
fs embed.FS
44+
)
45+
46+
func (s *Screen) Update() error {
47+
if time.Now().After(s.time) {
48+
s.time = s.time.Add(s.config.Interval)
49+
}
50+
51+
duration := time.Now().Sub(s.time).Round(time.Second).Abs()
52+
for _, controller := range s.controllers {
53+
controller.Update(duration)
54+
}
55+
56+
if inpututil.IsKeyJustPressed(ebiten.KeyBackspace) {
57+
s.time = time.Now()
58+
s.startTime = s.time
59+
}
60+
61+
if isPressed(ebiten.KeyLeft) {
62+
s.time = s.time.Add(-time.Second)
63+
}
64+
if isPressed(ebiten.KeyRight) {
65+
s.time = s.time.Add(time.Second)
66+
}
67+
return nil
68+
}
69+
70+
func isPressed(key ebiten.Key) bool {
71+
if ebiten.IsKeyPressed(ebiten.KeyShift) {
72+
return ebiten.IsKeyPressed(key)
73+
}
74+
return inpututil.IsKeyJustPressed(key)
75+
}
76+
77+
func (s *Screen) Draw(screen *ebiten.Image) {
78+
text.Draw(screen, fmt.Sprintf("Time left: %s", FormatTime(s.time)), s.font, 5, 25, color.White)
79+
ebitenutil.DebugPrintAt(screen, "Backspace: Reset time", 5, 45)
80+
ebitenutil.DebugPrintAt(screen, "<-: Remove one second", 5, 60)
81+
ebitenutil.DebugPrintAt(screen, "->: Add one second", 5, 75)
82+
ebitenutil.DebugPrintAt(screen, "^: Increase volume", 5, 90)
83+
ebitenutil.DebugPrintAt(screen, "v: Lower volume", 5, 105)
84+
ebitenutil.DebugPrintAt(screen, "M: Mute", 5, 120)
85+
ebitenutil.DebugPrintAt(screen, "Shift: Increase action", 5, 135)
86+
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Total Time: %s", FormatTime(s.startTime)), 5, 150)
87+
88+
for _, controller := range s.controllers {
89+
controller.Draw(screen)
90+
}
91+
92+
}
93+
94+
func FormatTime(t time.Time) string {
95+
seconds := time.Now().Sub(t).Round(time.Second).Abs()
96+
97+
minute := seconds / time.Minute
98+
seconds -= minute * time.Minute
99+
seconds = seconds / time.Second
100+
101+
return fmt.Sprintf("%02d:%02d", minute, seconds)
102+
}
103+
104+
func (s *Screen) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
105+
return outsideWidth, outsideHeight
106+
}
107+
108+
func main() {
109+
config := &Config{
110+
FirstAlarm: MustParseDuration(FirstAlarm),
111+
SecondAlarm: MustParseDuration(SecondAlarm),
112+
Interval: MustParseDuration(Interval),
113+
Volume: 0.1,
114+
}
115+
116+
err := config.Validate()
117+
if err != nil {
118+
log.Fatalf("failed to validate config: %s", err)
119+
}
120+
121+
font, err := loadTextFont()
122+
if err != nil {
123+
log.Fatalf("failed to load font: %s", err)
124+
}
125+
126+
firstAlert, err := NewAlert(config.FirstAlarm, "files/quack.mp3")
127+
if err != nil {
128+
log.Fatalf("failed to load alert: %s", err)
129+
}
130+
131+
secondAlert, err := NewAlert(config.SecondAlarm, "files/goat.mp3")
132+
if err != nil {
133+
log.Fatalf("failed to load alert: %s", err)
134+
}
135+
136+
sounds := NewSoundController(config.Volume)
137+
sounds.Add(firstAlert)
138+
sounds.Add(secondAlert)
139+
140+
s := &Screen{
141+
config: config,
142+
time: time.Now().Add(config.Interval),
143+
startTime: time.Now(),
144+
145+
controllers: []Controller{sounds},
146+
font: font,
147+
}
148+
149+
ebiten.SetWindowSize(200, 180)
150+
ebiten.SetWindowTitle(WindowTitle)
151+
ebiten.SetWindowFloating(true)
152+
153+
if err := ebiten.RunGame(s); err != nil {
154+
log.Fatal(err)
155+
}
156+
157+
}
158+
159+
func loadTextFont() (font.Face, error) {
160+
ttfFile, err := fs.ReadFile("files/tahoma.ttf")
161+
if err != nil {
162+
return nil, fmt.Errorf("failed to open file: %w", err)
163+
}
164+
tt, err := opentype.Parse(ttfFile)
165+
if err != nil {
166+
return nil, fmt.Errorf("failed to parse font: %w", err)
167+
}
168+
169+
tahoma, err := opentype.NewFace(tt, &opentype.FaceOptions{
170+
Size: 26,
171+
DPI: 72,
172+
Hinting: font.HintingVertical,
173+
})
174+
if err != nil {
175+
return nil, fmt.Errorf("failed to create new font face: %w", err)
176+
}
177+
// Adjust the line height.
178+
//mplusBigFont = text.FaceWithLineHeight(mplusBigFont, 54)
179+
180+
return tahoma, nil
181+
}

cmd/tibia-timer/sound.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/hajimehoshi/ebiten/v2"
8+
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
9+
)
10+
11+
type Sound interface {
12+
Component
13+
SetVolume(vol float64)
14+
}
15+
16+
type sound struct {
17+
volumeChanged bool
18+
mute bool
19+
volume float64
20+
21+
sounds []Sound
22+
}
23+
24+
func NewSoundController(volume float64) *sound {
25+
return &sound{
26+
volume: volume,
27+
volumeChanged: true,
28+
}
29+
}
30+
31+
func (s *sound) Add(sound Sound) {
32+
s.sounds = append(s.sounds, sound)
33+
}
34+
35+
func (s *sound) Update(duration time.Duration) {
36+
if s.volumeChanged {
37+
for _, sound := range s.sounds {
38+
sound.SetVolume(s.volume)
39+
}
40+
s.volumeChanged = false
41+
}
42+
43+
if isPressed(ebiten.KeyUp) && s.volume <= 1 {
44+
s.volume += .01
45+
s.volumeChanged = true
46+
}
47+
if isPressed(ebiten.KeyDown) && s.volume > 0.01 {
48+
s.volume -= .01
49+
s.volumeChanged = true
50+
}
51+
52+
if isPressed(ebiten.KeyM) {
53+
s.mute = !s.mute
54+
}
55+
56+
if s.mute {
57+
return
58+
}
59+
60+
for _, sound := range s.sounds {
61+
sound.Update(duration)
62+
}
63+
}
64+
65+
func (s *sound) Draw(screen *ebiten.Image) {
66+
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Volume: %s", s.FormatVolume()), 5, 30)
67+
}
68+
69+
func (s *sound) FormatVolume() string {
70+
if s.mute {
71+
return "mute"
72+
}
73+
return fmt.Sprintf("%.0f%%", s.volume*100)
74+
}

0 commit comments

Comments
 (0)