package game import ( "log" ) const ( DEFAULT_AVAIL_STREET_ACTIONS = 1 DEFAULT_AVAIL_MOVE_ACTIONS = 1 DEFAULT_AVAIL_ATTACK_ACTIONS = 1 ) type Unit struct { permanentBase Health int Movement Movement Attack Attack upkeep *ResourceCosts FullActions []*FullAction FreeActions []*FreeAction // Effects []Effects AvailStreetActions int AvailMoveActions int additionalMoveActions int AvailAttackActions int additionalAttackActions int } func NewUnit(card *Card, tile *Tile, owner *Player) *Unit { if !card.Type.IsUnit() { log.Panic(card.Name, " is not a unit") } var movement Movement movementDesc, found := card.Values["movement"] if !found { movement = INVALID_MOVEMENT() } else { movement = parseMovement(movementDesc) } upkeep := card.PlayCosts if upkeep == nil { upkeep = &ResourceCosts{fixed: 0} } var attack Attack if attackDesc, found := card.Values["attack"]; found { attack = parseAttack(attackDesc) } u := &Unit{ permanentBase: newPermanentBase(card, tile, owner), Health: int(card.Values["health"].(uint64)), Movement: movement, Attack: attack, upkeep: upkeep, AvailMoveActions: DEFAULT_AVAIL_MOVE_ACTIONS, AvailStreetActions: DEFAULT_AVAIL_STREET_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 (u *Unit) String() string { return FmtPermanent(u) } func NewUnitFromPath(cardPath string, tile *Tile, owner *Player) *Unit { return NewUnit(NewCard(cardPath), tile, owner) } func (u *Unit) UpkeepCost() int { return u.upkeep.Costs(u.controller.gameState) } func (u *Unit) resetBaseActions() { u.AvailStreetActions = DEFAULT_AVAIL_STREET_ACTIONS + u.additionalMoveActions u.AvailMoveActions = DEFAULT_AVAIL_MOVE_ACTIONS + u.additionalMoveActions u.AvailAttackActions = DEFAULT_AVAIL_ATTACK_ACTIONS + u.additionalAttackActions } func (u *Unit) onUpkeep() { u.resetBaseActions() getCardImplementation(u.Card()).onUpkeep(u) if u.Marks(UnitStates.Poison) > 0 { u.adjustMarks(UnitStates.Poison, 1) } if u.Marks(UnitStates.Paralysis) > 0 { u.adjustMarks(UnitStates.Paralysis, -1) u.tap() } } func (u *Unit) MoveRangeTiles() []*Tile { tiles := []*Tile{} m := u.controller.gameState.Map() graph := m.generateMovementRangeGraphFor(u) origin := TileOrContainingPermTile(u).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) AttackableTile(t *Tile) (bool, Attack) { if flexAttack := u.Attack.flexAttack; flexAttack != nil { if _, ok := flexAttack(u, t); ok { return true, u.Attack } } return IsPositionInRange(u.Tile().Position, t.Position, u.Attack.MaxRange()), u.Attack } func (u *Unit) AttackableTiles() []*Tile { // Units not on the map can not attack if u.tile == nil { return nil } m := u.controller.gameState.Map() var tilesInRange []*Tile if flexAttack := u.Attack.flexAttack; flexAttack != nil { tilesInRange = m.FilterTiles(func(t *Tile) bool { _, ok := flexAttack(u, t); return ok }) } else { tilesInRange = TilesInRange(m, u, u.Attack.MaxRange()) if u.Attack.MaxRange() == 1 { return tilesInRange } } // House tiles are not attackable from ranges > 1 aTiles := []*Tile{} for _, t := range tilesInRange { if DistanceBetweenPositions(t.Position, u.tile.Position) > 1 && t.Type == TileTypes.House { continue } aTiles = append(aTiles, t) } return aTiles } func (u *Unit) AttackablePermanents() []Permanent { permanents := []Permanent{} for _, t := range u.AttackableTiles() { if t.Permanent != nil && t.Permanent.Attackable() { permanents = append(permanents, t.Permanent) } } return permanents } func (u *Unit) AttackableEnemyPermanents() []Permanent { controller := u.Controller() permanents := []Permanent{} for _, t := range u.AttackableTiles() { if t.Permanent != nil && t.Permanent.Attackable() && t.Permanent.Controller() != controller { permanents = append(permanents, t.Permanent) } } return permanents } func (u *Unit) fight(p Permanent) { d, reachable := u.Attack.DamageForTile(u, p.Tile()) if reachable { DealDamage(u, p, d) } } func (u *Unit) IsDestroyed() bool { return u.damage >= u.Health || u.Marks(UnitStates.Poison) >= u.Health*2 } func (u *Unit) HasFullAction() bool { return len(u.FullActions) > 0 } func (u *Unit) tap() { u.AvailMoveActions = 0 u.AvailAttackActions = 0 } func (u *Unit) AvailSlowActions() (actions []Action) { // TODO: implement panic and rage marks if u.Marks(UnitStates.Panic) > 0 { } if u.Marks(UnitStates.Rage) > 0 { } if u.AvailMoveActions > 0 && u.Movement != INVALID_MOVEMENT() { actions = append(actions, NewMoveAction(u)) } if u.AvailStreetActions > 0 && u.Movement != INVALID_MOVEMENT() && u.Tile() != nil && u.Tile().Type == TileTypes.Street { actions = append(actions, NewStreetAction(u)) } m := u.Controller().gameState.Map() if u.AvailAttackActions > 0 && u.Attack.Valid() && len(u.AttackableEnemyPermanents()) > 0 { actions = append(actions, NewAttackAction(u)) } if u.AvailAttackActions > 0 && u.AvailMoveActions > 0 { for _, p := range u.FullActions { actions = append(actions, p) } } if u.AvailAttackActions > 0 || u.AvailMoveActions > 0 { for _, t := range TilesInRange(m, u, 1) { if t.Permanent == nil { continue } // Equipment Actions if equipment, ok := t.Permanent.(*Equipment); ok { actions = append(actions, NewEquipAction(u, equipment)) } for _, perm := range t.Permanent.Pile() { if equipment, ok := perm.(*Equipment); ok { actions = append(actions, NewEquipAction(u, equipment)) } } // Artifact Actions if t.Permanent.Card().Type.IsArtifact() { // ArtifactMoveActions are FullActions if u.AvailAttackActions > 0 && u.AvailMoveActions > 0 { actions = append(actions, newArtifactMoveAction(u, t.Permanent)) } // ArtifactSwitchActions replace the MoveAction if u.AvailMoveActions > 0 { // Only generate an ArtifactSwitchAction if the current // tiles are suitable for both permanents if t.Permanent != nil && t.Permanent.Card().Type.IsArtifact() && t.IsSuitableForCard(u.Card()) && u.Tile().IsSuitableForCard(t.Permanent.Card()) { actions = append(actions, newArtifactSwitchAction(u, t.Permanent)) } } } } } return actions } func (u *Unit) AvailFreeActions() (actions []Action) { for _, p := range u.FreeActions { actions = append(actions, p) } return } func (u *Unit) CurrentlyAvailActions() (actions []Action) { s := u.Controller().gameState if s.IsActivePlayer(u.Controller()) && s.ActivePhase() == Phases.ActionPhase { actions = append(actions, u.AvailSlowActions()...) } actions = append(actions, u.AvailFreeActions()...) return } func (u *Unit) AvailActions() (actions []Action) { actions = append(actions, u.AvailSlowActions()...) actions = append(actions, u.AvailFreeActions()...) return } 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) } func (u *Unit) removeFullAction(tag string) { for i, fa := range u.FullActions { if fa.tag != tag { continue } u.FullActions[i] = u.FullActions[len(u.FullActions)-1] u.FullActions = u.FullActions[:len(u.FullActions)-1] break } } func (u *Unit) adjustDamage(damage int) { if damage > 0 && u.Marks(UnitMarks.Ward) > 0 { u.adjustMarks(UnitMarks.Ward, -1) return } u.permanentBase.adjustDamage(damage) } func (u *Unit) equip(e *Equipment) { if e.containingPerm != nil { removePermanentFromPile(e.containingPerm, e) } else { leaveTileOrPile(e) } addPermanentToPile(u, e) }