package game import ( "slices" "muhq.space/muhqs-game/go/log" "muhq.space/muhqs-game/go/utils" ) type Trigger interface { Source() any String() string Card() *Card trigger(*LocalState, Event) ([]*TriggeredAction, bool) } type triggerBase struct { source any condition func(*LocalState, Event) (bool, bool) resolveFunc func(*LocalState, Event) ActionResolveFunc costFunc ActionCostFunc desc string } func (t *triggerBase) Source() any { return t.source } func (t *triggerBase) String() string { return t.desc } func (t *triggerBase) Card() *Card { switch t := t.source.(type) { case *Card: return t case Permanent: return t.Card() default: log.Panicf("Unhandled source type %v", t) return nil } } func (t *triggerBase) trigger(s *LocalState, e Event) ([]*TriggeredAction, bool) { triggered, remove := t.condition(s, e) if !triggered { return nil, remove } return []*TriggeredAction{newTriggeredAction(e, t, t.resolveFunc(s, e), t.costFunc)}, remove } func newUpkeepTrigger( src any, remove func(*LocalState, Event) bool, cond func(*LocalState, Event) bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, desc string, ) Trigger { t := triggerBase{source: src, resolveFunc: resolveFunc, desc: desc} t.condition = func(s *LocalState, event Event) (bool, bool) { triggered := event.eventType == EventTypes.PhaseChange && s.activePhase == Phases.UpkeepPhase && cond(s, event) removed := remove(s, event) return triggered, removed } return &t } func newTargetedTrigger(p Permanent, singleshot bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, desc string) Trigger { t := triggerBase{source: p, resolveFunc: resolveFunc, desc: desc} t.condition = func(_ *LocalState, event Event) (bool, bool) { c := event.eventType == EventTypes.Target && slices.Contains(event.affected, p.(any)) remove := singleshot && c return c, remove } return &t } func newDiscardTrigger(p Permanent, singleshot, singleturn bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, cond func(Event) bool, desc string) Trigger { t := triggerBase{source: p, resolveFunc: resolveFunc, desc: desc} t.condition = func(_ *LocalState, event Event) (bool, bool) { if event.eventType == EventTypes.Eot && singleturn { return false, true } if event.eventType != EventTypes.Discard { return false, false } c := cond(event) remove := singleshot && c return c, remove } return &t } func newOwnDiscardTrigger(p Permanent, singleshot, singleturn bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, desc string) Trigger { return newDiscardTrigger(p, singleshot, singleturn, resolveFunc, func(e Event) bool { return e.sources[0] == p.Controller() }, desc) } func newDeathTrigger(p Permanent, singleshot bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, permCond func(Permanent) bool, desc string) Trigger { t := triggerBase{source: p, resolveFunc: resolveFunc, desc: desc} t.condition = func(_ *LocalState, event Event) (bool, bool) { if event.eventType != EventTypes.Sacrifice && event.eventType != EventTypes.Destruction { return false, false } destroyedPerm := event.affected[0].(Permanent) c := permCond(destroyedPerm) remove := singleshot && c return c, remove } return &t } func newOwnDeathTrigger(p Permanent, resolveFunc func(*LocalState, Event) ActionResolveFunc, desc string) Trigger { return newDeathTrigger(p, true, resolveFunc, func(destroyedPerm Permanent) bool { return p == destroyedPerm }, desc) } func newEnemyDeathTrigger(p Permanent, resolveFunc func(*LocalState, Event) ActionResolveFunc, desc string) Trigger { return newDeathTrigger(p, true, resolveFunc, func(destroyedPerm Permanent) bool { return p.Controller().IsEnemy(destroyedPerm.Controller()) }, desc) } func newDamageDealtTrigger(source any, singleshot bool, cond func(Event) bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, desc string) Trigger { t := triggerBase{source: source, resolveFunc: resolveFunc, desc: desc} t.condition = func(_ *LocalState, event Event) (bool, bool) { if event.eventType != EventTypes.DamageDealt { return false, false } return cond(event), singleshot } return &t } func newDamageDealtByTrigger(source any, singleshot bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, desc string) Trigger { return newDamageDealtTrigger(source, singleshot, func(e Event) bool { return slices.Contains(e.sources, source) }, resolveFunc, desc) } func newDamageDealtToTrigger(p Permanent, singleshot bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, desc string) Trigger { return newDamageDealtTrigger(p, singleshot, func(e Event) bool { return slices.Contains(e.affected, p.(any)) }, resolveFunc, desc) } func (s *LocalState) addTrigger(trigger Trigger) { s.triggers = append(s.triggers, trigger) } func (s *LocalState) removeTriggers(triggers []Trigger) { for _, t := range triggers { s.triggers = utils.RemoveFromUnorderedSlice(s.triggers, t) } } func (s *LocalState) removeTrigger(trigger Trigger) { s.removeTriggers([]Trigger{trigger}) } func (s *LocalState) handleTriggers() { var actions []*TriggeredAction var triggersToRemove []Trigger for _, e := range s.events { for _, t := range s.triggers { a, remove := t.trigger(s, e) if a != nil { actions = append(actions, a...) } if remove { triggersToRemove = append(triggersToRemove, t) } } } // Reset occured events s.events = nil s.removeTriggers(triggersToRemove) if len(actions) > 0 { orderedActions := s.orderTriggeredActions(actions) for _, a := range orderedActions { s.declareAction(a) } } }