-
Notifications
You must be signed in to change notification settings - Fork 28
/
agent.go
190 lines (153 loc) · 5.51 KB
/
agent.go
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package core
import (
"reflect"
"strconv"
"strings"
"github.com/wowsims/sod/sim/core/proto"
)
// Agent can be thought of as the 'Player', i.e. the thing controlling the Character.
// This is the interface implemented by each class/spec.
type Agent interface {
// The Character controlled by this Agent.
GetCharacter() *Character
// Called once after all Players/Pets/Targets have finished the construction phase.
// Use this to register spells and any initialization steps that require
// other raid members or auras.
Initialize()
// Updates the input Buffs to include raid-wide buffs provided by this Agent.
AddRaidBuffs(raidBuffs *proto.RaidBuffs)
// Updates the input Buffs to include party-wide buffs provided by this Agent.
AddPartyBuffs(partyBuffs *proto.PartyBuffs)
// All talent and runes stats / auras should be added within this callback. This makes sure
// talents are applied at the right time so we can calculate groups of stats.
ApplyTalents()
ApplyRunes()
// Returns this Agent to its initial state. Called before each Sim iteration
// and once after the final iteration.
Reset(sim *Simulation)
// Custom factories for APL values and actions, for cases where the value/action
// involves class or spec-specific behavior.
//
// Should return nil when the config doesn't match any custom behaviors.
NewAPLValue(rot *APLRotation, config *proto.APLValue) APLValue
NewAPLAction(rot *APLRotation, config *proto.APLAction) APLActionImpl
// Implements custom rotation behavior. Usually for pets and targets but can be used
// for players too.
ExecuteCustomRotation(sim *Simulation)
}
type ActionID struct {
// Only one of these should be set.
SpellID int32
ItemID int32
OtherID proto.OtherAction
Tag int32
}
func (actionID ActionID) IsEmptyAction() bool {
return actionID.SpellID == 0 && actionID.ItemID == 0 && actionID.OtherID == 0
}
func (actionID ActionID) IsSpellAction(spellID int32) bool {
return actionID.SpellID == spellID
}
func (actionID ActionID) IsItemAction(itemID int32) bool {
return actionID.ItemID == itemID
}
func (actionID ActionID) IsOtherAction(otherID proto.OtherAction) bool {
return actionID.OtherID == otherID
}
func (actionID ActionID) SameActionIgnoreTag(other ActionID) bool {
return actionID.SpellID == other.SpellID && actionID.ItemID == other.ItemID && actionID.OtherID == other.OtherID
}
func (actionID ActionID) SameAction(other ActionID) bool {
return actionID.SameActionIgnoreTag(other) && actionID.Tag == other.Tag
}
func (actionID ActionID) String() string {
var sb strings.Builder
sb.WriteString("{")
if actionID.SpellID != 0 {
sb.WriteString("SpellID: ")
sb.WriteString(strconv.Itoa(int(actionID.SpellID)))
} else if actionID.ItemID != 0 {
sb.WriteString("ItemID: ")
sb.WriteString(strconv.Itoa(int(actionID.ItemID)))
} else if actionID.OtherID != 0 {
sb.WriteString("OtherID: ")
sb.WriteString(strconv.Itoa(int(actionID.OtherID)))
}
if actionID.Tag != 0 {
sb.WriteString(", Tag: ")
sb.WriteString(strconv.Itoa(int(actionID.Tag)))
}
sb.WriteString("}")
return sb.String()
}
// Returns a new ActionID with the corresponding Tag value.
func (actionID ActionID) WithTag(tag int32) ActionID {
newID := actionID
newID.Tag = tag
return newID
}
func (actionID ActionID) ToProto() *proto.ActionID {
protoID := &proto.ActionID{
Tag: actionID.Tag,
}
if actionID.SpellID != 0 {
protoID.RawId = &proto.ActionID_SpellId{SpellId: actionID.SpellID}
} else if actionID.ItemID != 0 {
protoID.RawId = &proto.ActionID_ItemId{ItemId: actionID.ItemID}
} else if actionID.OtherID != 0 {
protoID.RawId = &proto.ActionID_OtherId{OtherId: actionID.OtherID}
}
return protoID
}
func ProtoToActionID(protoID *proto.ActionID) ActionID {
if protoID == nil {
panic("protoID is nil")
}
return ActionID{
ItemID: protoID.GetItemId(),
SpellID: protoID.GetSpellId(),
OtherID: protoID.GetOtherId(),
Tag: protoID.Tag,
}
}
type AgentFactory func(*Character, *proto.Player) Agent
type SpecSetter func(*proto.Player, interface{})
var agentFactories = make(map[string]AgentFactory)
var specSetters = make(map[string]SpecSetter)
var configSpecs = make(map[string]proto.Spec)
func PlayerProtoToSpec(player *proto.Player) proto.Spec {
typeName := reflect.TypeOf(player.GetSpec()).Elem().Name()
return configSpecs[typeName]
}
func RegisterAgentFactory(emptyOptions interface{}, spec proto.Spec, factory AgentFactory, specSetter SpecSetter) {
typeName := reflect.TypeOf(emptyOptions).Name()
if _, ok := agentFactories[typeName]; ok {
panic("Aleady registered agent factory: " + typeName)
}
//fmt.Printf("Registering type: %s", typeName)
agentFactories[typeName] = factory
specSetters[typeName] = specSetter
configSpecs[typeName] = spec
}
// Constructs a new Agent.
func NewAgent(party *Party, partyIndex int, player *proto.Player) Agent {
typeName := reflect.TypeOf(player.GetSpec()).Elem().Name()
factory, ok := agentFactories[typeName]
if !ok {
panic("No agent factory for type: " + typeName)
}
character := NewCharacter(party, partyIndex, player)
return factory(&character, player)
}
// Applies the spec options to the given player. This is only necessary because
// the generated proto code does not export oneof interface types.
// Player is returned so this function can be used in-line with player creation.
func WithSpec(player *proto.Player, spec interface{}) *proto.Player {
typeName := reflect.TypeOf(spec).Elem().Name()
specSetter, ok := specSetters[typeName]
if !ok {
panic("No spec setter for type: " + typeName)
}
specSetter(player, spec)
return player
}