func TestEntitiesUpdate(t *testing.T) { tick := message.Tick(42) entity1 := message.NewEntity() entity1.Id = 69 entity1.Position.X = 22 entity1.Position.Y = 65 diff1 := message.NewEntityDiff() diff1.Position = true entity2 := message.NewEntity() entity2.Id = 76 entity2.Speed.Angle = 67 entity2.Speed.Norm = 43 diff2 := message.NewEntityDiff() diff2.SpeedAngle = true diff2.SpeedNorm = true entities := []*message.Entity{entity1, entity2} diffs := []*message.EntityDiff{diff1, diff2} testMessage(t, message.Types["entities_update"], func(w io.Writer) error { return builder.SendEntitiesUpdate(w, tick, entities, diffs) }, func(conn *message.Conn, t *testing.T) { rt, rentities, rdiffs := handler.ReadEntitiesUpdate(conn) if rt != tick { t.Fatal("Sent tick", tick, "but received", rt) } if len(entities) != len(rentities) { t.Fatal("Sent", len(entities), "entities but received", len(rentities)) } for i, entity := range entities { diff := diffs[i] rentity := rentities[i] rdiff := rdiffs[i] if !diff.Equals(rdiff) { t.Fatal("Sent diff", diff, " at offset", i, "but received", rdiff) } if !entity.EqualsWithDiff(rentity, diff) { t.Fatal("Sent entity", entity, " at offset", i, "with diff", diff, "but received", rentity) } } }) }
// Ensure that there is only one delta per entity in the deltas list. // Warning: some information is lost, for instance if an entity is created then // deleted, an empty delta is added to the list. func flattenDeltas(deltas *delta.List) []*Delta { m := make(map[message.EntityId]*Delta) for e := deltas.First(); e != nil; e = e.Next() { d := e.Value.(*Delta) if current, ok := m[d.EntityId]; ok { // TODO: check that current.To and d.From are compatible if d.Diff != nil && current.To != nil { current.To.ApplyDiff(d.Diff, d.To) } else { // Deep copy To since it can be modified by this loop current.To = d.To if current.To != nil { current.To = current.To.Copy() } } } else { // Deep copy To since it can be modified by this loop to := d.To if to != nil { to = to.Copy() } m[d.EntityId] = &Delta{ EntityId: d.EntityId, Diff: message.NewEntityDiff(), From: d.From, To: to, } } if d.Diff != nil { m[d.EntityId].Diff.Merge(d.Diff) } } l := make([]*Delta, len(m)) i := 0 for _, d := range m { l[i] = d i++ } return l }
func (e *Engine) moveEntities(t message.AbsoluteTick) { for _, ent := range e.entity.List() { req, collidesWith := e.mover.UpdateEntity(ent, t) // [GAME-SPECIFIC] Destroy balls when they collide if collidesWith != nil && ent.Type == game.BallEntity { req := entity.NewDeleteRequest(t, ent.Id) if err := e.entity.AcceptRequest(req); err != nil { panic(err) // This shouldn't happen } // If the ball collides with a player, decrease his health if collidesWith, ok := collidesWith.(*message.Entity); ok && collidesWith.Type == game.PlayerEntity { sender := ent.Attributes[game.SenderAttr].(message.EntityId) if sender != collidesWith.Id { health := collidesWith.Attributes[game.HealthAttr].(game.Health) updated := entity.New() updated.Attributes[game.HealthAttr] = health - 1 diff := message.NewEntityDiff() diff.Attributes = true req := entity.NewUpdateRequest(t, updated, diff) if err := e.entity.AcceptRequest(req); err != nil { panic(err) // This shouldn't happen } } } continue } if req != nil { // Let's assume mover already did all security checks if err := e.entity.AcceptRequest(req); err != nil { // This should not fail panic(err) } } } }