aboutsummaryrefslogtreecommitdiff
path: root/go/game/attack.go
blob: 0e5c66ab6286d7d5acce07ef02857fc7b4ee5169 (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
package game

import (
	"fmt"
	"log"
	"strconv"
	"strings"
)

type FlexAttack = func(*Unit, *Tile) (int, bool)

// Attack contains all damage values for each attackable range.
// The damage value for range 1 is stored at index 0, the value for range 2 at index 1 and so on.
type Attack struct {
	attacks    []int
	flexAttack FlexAttack
}

// Valid reports if there are attackable ranges.
func (a Attack) Valid() bool {
	return len(a.attacks) > 0 || a.flexAttack != nil
}

// DamageForTile returns the damage inflicted by this attack to a tile
func (a Attack) DamageForTile(u *Unit, t *Tile) (d int, ok bool) {
	o := u.Tile().Position
	trgt := t.Position
	dist := DistanceBetweenPositions(o, trgt)

	if a.flexAttack != nil {
		d, ok = a.flexAttack(u, t)
	} else {
		d = a.DamageInRange(dist)
		ok = IsPositionInRange(o, trgt, a.MaxRange())
	}

	if !ok {
		return
	}

	if dist > 1 && t.Type == TileTypes.House {
		return 0, false
	}

	return
}

// MaxRange returns the biggest attackable range.
func (a Attack) MaxRange() int {
	return len(a.attacks)
}

// Copy returns a copy of the attack
func (a Attack) Copy() Attack {
	copy := Attack{}
	copy.attacks = append(copy.attacks, a.attacks...)
	return copy
}

// DamageInRange returns the damage in a certain range.
func (a Attack) DamageInRange(r int) int {
	if r == 0 || r > a.MaxRange() {
		return 0
	}

	return a.attacks[r-1]
}

// Ranged reports if the attack has more range than 1.
func (a Attack) Ranged() bool {
	return a.MaxRange() > 1
}

// Extend increases the MaxRange by amount.
func (a *Attack) Extend(amount int) {
	d := a.DamageInRange(a.MaxRange())
	for range amount {
		a.attacks = append(a.attacks, d)
	}
}

// Shorten decreases the MaxRange by amount.
func (a *Attack) Shorten(amount int) {
	a.attacks = a.attacks[:len(a.attacks)-amount]
}

func (a Attack) String() string {
	if !a.Valid() {
		return "invalid attack"
	}

	if len(a.attacks) == 1 {
		return fmt.Sprintf("%d", a.attacks[0])
	}

	r := a.MaxRange()
	return fmt.Sprintf("%d range %d", a.DamageInRange(r), r)
}

func parseAttack(attack any) Attack {
	if damage, ok := attack.(uint64); ok {
		return Attack{attacks: []int{int(damage)}}
	}

	attackStr := attack.(string)
	tokens := strings.Split(attackStr, " ")
	a, err := strconv.Atoi(tokens[0])
	// TODO: think about panicking here
	if err != nil {
		log.Panicf("Attack %s has no valid Damage value\n", attackStr)
	}

	r := 1
	if len(tokens) == 3 {
		r, err = strconv.Atoi(tokens[2])
		if err != nil {
			log.Panicf("Attack %s has no valid Range value\n", attackStr)
		}
	}

	attacks := make([]int, 0, r)
	for range r {
		attacks = append(attacks, a)
	}

	return Attack{attacks: attacks}
}

func flexibleRangeAttack(constraint func(*Unit, *Tile) bool) FlexAttack {
	return func(u *Unit, t *Tile) (int, bool) {
		baseAttack := u.Attack
		pos := u.Tile().Position
		d := DistanceBetweenPositions(pos, t.Position)
		if IsPositionInRange(pos, t.Position, baseAttack.MaxRange()) {
			return baseAttack.DamageInRange(d), true
		}

		if constraint(u, t) {
			return baseAttack.DamageInRange(baseAttack.MaxRange()), true
		}

		return 0, false
	}
}

func pikeConstraint(u *Unit, t *Tile) bool {
	if o, ok := t.Permanent.(*Unit); ok {
		d := DistanceBetweenPositions(u.Tile().Position, t.Position)

		return d < 3 && o.Movement.Range > 2
	}
	return false
}

func pikeAttack() FlexAttack {
	return flexibleRangeAttack(pikeConstraint)
}