diff --git a/ent/class.go b/ent/class.go index 6cb04c2..546c331 100644 --- a/ent/class.go +++ b/ent/class.go @@ -1,6 +1,7 @@ package ent import ( + "fmt" "github.com/jordanorelli/hyperstone/dota" ) @@ -15,6 +16,10 @@ func (c *Class) New() *Entity { return &Entity{Class: c} } +func (c Class) String() string { + return fmt.Sprintf("{%s %d}", c.Name, c.Version) +} + type classId struct { name Symbol version int diff --git a/ent/dict.go b/ent/dict.go index 33204b7..24c1ce6 100644 --- a/ent/dict.go +++ b/ent/dict.go @@ -2,44 +2,114 @@ package ent import ( "fmt" + + "github.com/golang/protobuf/proto" + "github.com/jordanorelli/hyperstone/bit" + "github.com/jordanorelli/hyperstone/dota" ) -type Context struct { +// Dict corresponds to the edict_t in Valve's documentation for the Source +// engine. See here: https://developer.valvesoftware.com/wiki/Edict_t +// +// From the Valve docs: +// edict_t ("entity dictionary") is an interface struct that allows entities +// to cross the client/server divide: with one attached, an entity has the +// same index at both ends. The edict also manages the state of the entity's +// DataTable and provides a common representation across all DLLs. It cannot +// be used on the client. +type Dict struct { *Namespace entities map[int]Entity + br *bit.BufReader } -func NewContext() *Context { - return &Context{Namespace: new(Namespace), entities: make(map[int]Entity)} +func NewDict() *Dict { + return &Dict{ + Namespace: new(Namespace), + entities: make(map[int]Entity), + br: new(bit.BufReader), + } } -func (c *Context) CreateEntity(id int, r bit.Reader) error { - classId := int(c.readClassId(r)) - if len(c.Namespace.classes) == 0 { +// creates an entity with the provided id. the entity's contents data are read +// off of the Dict's internal bit stream br +func (d *Dict) createEntity(id int) error { + classId := int(d.readClassId(d.br)) + if len(d.Namespace.classes) == 0 { return fmt.Errorf("unable to create entity %d: namespace has no classes", id) } - r.ReadBits(17) // ??? - classV := int(bit.ReadVarInt(r)) - className := c.classIds[classId] - class := c.Class(className, classV) + d.br.ReadBits(17) // ??? + classV := int(bit.ReadVarInt(d.br)) + className := d.classIds[classId] + class := d.Class(className, classV) if class == nil { return fmt.Errorf("unable to create entity %d: no class found for class name %s, version %d", className, classV) } Debug.Printf("create entity id: %d classId: %d className: %v class: %v\n", id, classId, className, class) e := class.New() - e.Read(r) + e.Read(d.br) return nil } -func (c *Context) UpdateEntity(id int, r bit.Reader) { +func (d *Dict) updateEntity(id int) error { Debug.Printf("update entity id: %d\n", id) + return nil } -func (c *Context) DeleteEntity(id int) { +func (d *Dict) deleteEntity(id int) error { Debug.Printf("delete entity id: %d\n", id) + return nil } -func (c *Context) LeaveEntity(id int) { +func (d *Dict) leaveEntity(id int) error { Debug.Printf("leave entity id: %d\n", id) + return nil +} + +func (d *Dict) Handle(m proto.Message) { + switch v := m.(type) { + case *dota.CDemoSendTables: + d.mergeSendTables(v) + + case *dota.CDemoClassInfo: + d.mergeClassInfo(v) + + case *dota.CSVCMsg_PacketEntities: + d.mergeEntities(v) + } +} + +func (d *Dict) mergeEntities(m *dota.CSVCMsg_PacketEntities) error { + data := m.GetEntityData() + + Debug.Printf("packet header MaxEntries: %d UpdatedEntries: %v IsDelta: %t UpdateBaseline: %t Baseline: %d DeltaFrom: %d PendingFullFrame: %t ActiveSpawngroupHandle: %d", m.GetMaxEntries(), m.GetUpdatedEntries(), m.GetIsDelta(), m.GetUpdateBaseline(), m.GetBaseline(), m.GetDeltaFrom(), m.GetPendingFullFrame(), m.GetActiveSpawngroupHandle()) + + d.br.SetSource(data) + id := -1 + // for i := 0; i < int(m.GetUpdatedEntries()); i++ { + for i := 0; i < 1; i++ { + id++ + // there may be a jump indicator, indicating how many id positions + // to skip. + id += int(bit.ReadUBitVar(d.br)) + + // next two bits encode one of four entity mutate operations + var fn func(int) error + switch d.br.ReadBits(2) { + case 0: + fn = d.updateEntity + case 1: + fn = d.leaveEntity + case 2: + fn = d.createEntity + case 3: + fn = d.deleteEntity + } + + if err := fn(id); err != nil { + return fmt.Errorf("entity merge error: %v", err) + } + } + return nil } diff --git a/ent/namespace.go b/ent/namespace.go index 5112d79..13a6552 100644 --- a/ent/namespace.go +++ b/ent/namespace.go @@ -27,7 +27,7 @@ type Namespace struct { } // Merges in the ClassInfo data found in the replay protobufs -func (n *Namespace) MergeClassInfo(ci *dota.CDemoClassInfo) { +func (n *Namespace) mergeClassInfo(ci *dota.CDemoClassInfo) { if n.classIds == nil { n.classIds = make(map[int]string, len(ci.GetClasses())) } @@ -37,7 +37,9 @@ func (n *Namespace) MergeClassInfo(ci *dota.CDemoClassInfo) { n.idBits = int(math.Floor(math.Log2(float64(len(n.classIds))))) + 1 } -func (n *Namespace) MergeSendTables(st *dota.CDemoSendTables) { +// merges the send table data found in the replay protobufs. The send table +// data contains a specification for an entity type system. +func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) { // sendtables only has one field, a binary data field. data := st.GetData() br := bit.NewBytesReader(data) diff --git a/entities.go b/entities.go deleted file mode 100644 index e71b8ed..0000000 --- a/entities.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "fmt" - "github.com/golang/protobuf/proto" - "github.com/jordanorelli/hyperstone/bit" - "github.com/jordanorelli/hyperstone/dota" - "github.com/jordanorelli/hyperstone/ent" -) - -// type CSVCMsg_PacketEntities struct { -// MaxEntries *int32 -// UpdatedEntries *int32 -// IsDelta *bool -// UpdateBaseline *bool -// Baseline *int32 -// DeltaFrom *int32 -// EntityData []byte -// PendingFullFrame *bool -// ActiveSpawngroupHandle *uint32 -// MaxSpawngroupCreationsequence *uint32 -// } - -var ctx = ent.NewContext() - -func dumpEntities(m proto.Message) { - switch v := m.(type) { - case *dota.CDemoSendTables: - ctx.MergeSendTables(v) - - case *dota.CDemoClassInfo: - ctx.MergeClassInfo(v) - - case *dota.CSVCMsg_PacketEntities: - data := v.GetEntityData() - var datap []byte - if len(data) > 32 { - datap = data[:32] - } else { - datap = data - } - - fmt.Printf("{MaxEntries: %d UpdatedEntries: %v IsDelta: %t UpdateBaseline: %t Baseline: %d DeltaFrom: %d EntityData: %x PendingFullFrame: %t ActiveSpawngroupHandle: %d}\n", v.GetMaxEntries(), v.GetUpdatedEntries(), v.GetIsDelta(), v.GetUpdateBaseline(), v.GetBaseline(), v.GetDeltaFrom(), datap, v.GetPendingFullFrame(), v.GetActiveSpawngroupHandle()) - - br := bit.NewBytesReader(data) - id := -1 - // for i := 0; i < int(v.GetUpdatedEntries()); i++ { - for i := 0; i < 1; i++ { - id++ - // there may be a jump indicator, indicating how many id positions - // to skip. - id += int(bit.ReadUBitVar(br)) - - // next two bits encode one of four entity mutate operations - switch br.ReadBits(2) { - case 0: - ctx.UpdateEntity(id, br) - case 1: - ctx.LeaveEntity(id) - case 2: - ctx.CreateEntity(id, br) - case 3: - ctx.DeleteEntity(id) - } - } - } -} diff --git a/main.go b/main.go index 7bf217e..95279e9 100644 --- a/main.go +++ b/main.go @@ -141,7 +141,9 @@ func main() { case "class-info": handle = dumpClasses case "entities": - handle = dumpEntities + ent.Debug = log.New(os.Stdout, "", 0) + d := ent.NewDict() + handle = d.Handle default: bail(1, "no such action: %s", flag.Arg(0)) }