package game import ( "log" ) const ( DEFAULT_AVAIL_MOVE_ACTIONS = 1 DEFAULT_AVAIL_ATTACK_ACTIONS = 1 ) type Unit struct { PermanentBase Health int Movement Movement Attack Attack Upkeep int FullActions []*FullAction FreeActions []*FreeAction // Effects []Effects AvailMoveActions int additionalMoveActions int AvailAttackActions int additionalAttackActions int } func NewUnit(card *Card, tile *Tile, owner *Player) *Unit { var movement Movement movementDesc, found := card.Values["movement"] if !found { movement = INVALID_MOVEMENT() } else { movement = parseMovement(movementDesc) } upkeep, ok := card.Values["upkeep"].(int) if !ok { upkeep = 0 } var attack Attack if attackDesc, found := card.Values["attack"]; found { attack = parseAttack(attackDesc) } u := &Unit{ PermanentBase: NewPermanentBase(card, tile, owner), Health: card.Values["health"].(int), Movement: movement, Attack: attack, Upkeep: upkeep, AvailMoveActions: DEFAULT_AVAIL_MOVE_ACTIONS, AvailAttackActions: DEFAULT_AVAIL_ATTACK_ACTIONS, } // TODO: parse effect for additional actions u.FullActions = card.Impl.fullActions(u) u.FreeActions = card.Impl.freeActions(u) return u } func NewUnitFromPath(cardPath string, tile *Tile, owner *Player) *Unit { return NewUnit(NewCard(cardPath), tile, owner) } func (u *Unit) resetBaseActions() { u.AvailMoveActions = DEFAULT_AVAIL_MOVE_ACTIONS + u.additionalMoveActions u.AvailAttackActions = DEFAULT_AVAIL_ATTACK_ACTIONS + u.additionalAttackActions } func (u *Unit) onUpkeep() { // TODO: handle state marks u.resetBaseActions() } func (u *Unit) move(s *State, tile *Tile) { if !u.IsAvailableTile(tile) { log.Fatalf("moving to not available tile %v", tile) } if u.containingPerm != nil { RemovePermanentFromPile(u.containingPerm, u) } else { leaveTile(u) } if tile.Permanent == nil { enterTile(u, tile) } else { // crew permanent at tile AddPermanentToPile(tile.Permanent, u) u.tile = nil } } func (u *Unit) TileOrContainingPermTile() *Tile { tile := u.tile if tile == nil { tile = u.containingPerm.Tile() } return tile } func (u *Unit) MoveRangeTiles(m *Map) []*Tile { tiles := []*Tile{} // TODO: build graph only containing possible tile in movement range graph := m.generateMapGraphFor(u) origin := u.TileOrContainingPermTile().Position for _, pos := range PositionsInRange(origin, u.Movement.Range, false) { tile := m.TileAt(pos) if tile == nil || !u.IsAvailableTile(tile) { continue } path, err := findPathTo(graph, u, pos) if err != nil { continue } if path.Distance/2 > int64(u.Movement.Range) { continue } tiles = append(tiles, tile) } return tiles } func (u *Unit) AttackableTiles(m *Map) []*Tile { // Units not on the map can not attack if u.tile == nil { return nil } return TilesInRange(m, u, u.Attack.MaxRange()) } func (u *Unit) AttackablePermanents(m *Map) []Permanent { permanents := []Permanent{} for _, t := range u.AttackableTiles(m) { if t.Permanent != nil && t.Permanent.Attackable() { permanents = append(permanents, t.Permanent) } } return permanents } func (u *Unit) AttackableEnemyPermanents(m *Map) []Permanent { controller := u.Controller() permanents := []Permanent{} for _, t := range u.AttackableTiles(m) { if t.Permanent != nil && t.Permanent.Attackable() && t.Permanent.Controller() != controller { permanents = append(permanents, t.Permanent) } } return permanents } func (u *Unit) Fight(p Permanent) { r := DistanceBetweenPermanents(p, u) DealDamage(u, p, u.Attack.DamageInRange(r)) } func (u *Unit) IsDestroyed() bool { return u.damage >= u.Health } func (u *Unit) Attackable() bool { // Only units on the map are attackable return u.tile != nil } func (u *Unit) HasFullAction() bool { return len(u.FullActions) > 0 } func (u *Unit) FullAction() { } func (u *Unit) AvailActions() (actions []Action) { if u.AvailMoveActions > 0 && u.Movement != INVALID_MOVEMENT() { actions = append(actions, NewMoveAction(u)) } m := u.Controller().gameState.Map if u.AvailAttackActions > 0 && !INVALID_ATTACK(u.Attack) && len(u.AttackableEnemyPermanents(m)) > 0 { actions = append(actions, NewAttackAction(u)) } if u.AvailAttackActions > 0 && u.AvailMoveActions > 0 { for _, p := range u.FullActions { actions = append(actions, p) } } for _, p := range u.FreeActions { actions = append(actions, p) } return actions } func (u *Unit) onPile(containing Permanent) { u.PermanentBase.onPile(containing) if baseUnit, ok := containing.(*Unit); ok { baseUnitAttackRange := baseUnit.Attack.MaxRange() for r, a := range u.Attack.attacks { if r > baseUnitAttackRange-1 { baseUnit.Attack.attacks = append(baseUnit.Attack.attacks, a) } else { baseUnit.Attack.attacks[r] += a } } } } func (u *Unit) onUnpile(containing Permanent) { u.PermanentBase.onUnpile(u) if baseUnit, ok := containing.(*Unit); ok { for r, a := range baseUnit.Attack.attacks { if a == u.Attack.attacks[r] { // Remove all further attacks since they must all be from the // dropped unit baseUnit.Attack.attacks = baseUnit.Attack.attacks[:r] break } else { baseUnit.Attack.attacks[r] -= u.Attack.attacks[r] } } } } func (u *Unit) onDrop(containing Permanent) { u.onUnpile(containing) }