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) }