Beispiel #1
0
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)
			}
		}
	}
}
Beispiel #2
0
func (e *Engine) executeTick(currentTick message.AbsoluteTick) {
	entityFrontend := e.entity.Frontend()
	actionFrontend := e.action.Frontend()

	// Process requests from clients
	minTick := currentTick - message.MaxRewind
	if message.MaxRewind > currentTick {
		minTick = 0
	}
	acceptedMinTick := currentTick
	accepted := list.New()
	for {
		var req message.Request

		// Get last request
		noMore := false
		select {
		case req = <-entityFrontend.Creates:
		case req = <-entityFrontend.Updates:
		case req = <-entityFrontend.Deletes:
		case req = <-actionFrontend.Executes:
		default:
			noMore = true
		}
		if noMore {
			break
		}

		t := req.GetTick()
		if t == message.InvalidTick {
			req.Done(errors.New("Invalid tick"))
			log.Println("Warning: Dropped request, invalid tick")
			continue
		}
		if t < minTick {
			req.Done(errors.New("Request is too old"))
			log.Println("Warning: Dropped request, too old", currentTick-t)
			continue
		}
		if t > currentTick {
			req.Done(errors.New("Request is in the future"))
			log.Println("Warning: Dropped request, in the future", currentTick-t)
			continue
		}
		if t < acceptedMinTick {
			acceptedMinTick = t
		}

		// Append request to the list, keeping it ordered
		inserted := false
		for e := accepted.Front(); e != nil; e = e.Next() {
			r := e.Value.(message.Request)

			if r.GetTick() > req.GetTick() {
				accepted.InsertBefore(req, e)
				inserted = true
				break
			}
		}
		if !inserted {
			accepted.PushBack(req)
		}
	}

	if accepted.Len() > 0 {
		//log.Println("Accepted", accepted.Len(), "requests from clients")
	}

	// Initiate lag compensation if necessary
	if acceptedMinTick < currentTick {
		log.Println("Back to the past!", currentTick, acceptedMinTick)
		e.terrain.Rewind(e.terrain.GetTick() - acceptedMinTick)
		e.entity.Rewind(e.entity.GetTick() - acceptedMinTick)
	}

	// Redo all deltas until now
	entDeltas := e.entity.Deltas()
	trnDeltas := e.terrain.Deltas()
	entEl := entDeltas.FirstAfter(acceptedMinTick)
	trnEl := trnDeltas.FirstAfter(acceptedMinTick)
	var ed *entity.Delta
	var td *terrain.Delta
	for entEl != nil || trnEl != nil {
		if entEl != nil {
			ed = entEl.Value.(*entity.Delta)
		} else {
			ed = nil
		}
		if trnEl != nil {
			td = trnEl.Value.(*terrain.Delta)
		} else {
			td = nil
		}

		// TODO: replace terain history by actions history
		if ed == nil || (td != nil && ed.GetTick() >= td.GetTick()) {
			// Process terrain delta
			trnEl = trnEl.Next()

			// Compute new entities positions just before the terrain change
			e.moveEntities(td.GetTick() - 1)

			// Redo terrain change
			e.terrain.Redo(td)
		}
		if td == nil || (ed != nil && ed.GetTick() <= td.GetTick()) {
			// Process entity delta

			// Do not redo deltas not triggered by the user
			// These are the ones that will be computed again
			if !ed.Requested() {
				entDeltas.Remove(entEl)
				entEl = entEl.Next()
				continue
			}

			entEl = entEl.Next()

			// Compute new entities positions
			e.moveEntities(ed.GetTick())

			// Accept new requests at the correct tick
			for el := accepted.Front(); el != nil; el = el.Next() {
				req := el.Value.(message.Request)

				if req.GetTick() >= ed.GetTick() {
					break
				}

				e.processRequest(req)
				accepted.Remove(el)
			}

			e.processRequest(ed.Request())
		}
	}

	// Accept requests whose tick is > last delta tick
	for el := accepted.Front(); el != nil; el = el.Next() {
		req := el.Value.(entity.Request)
		e.moveEntities(req.GetTick())
		e.processRequest(req)
	}

	// Compute new entities positions
	e.moveEntities(currentTick)

	// Destroy entities that are not alive anymore
	// TODO: history support
	for _, ent := range e.entity.List() {
		if val, ok := ent.Attributes[game.TicksLeftAttr]; ok {
			ttl := val.(game.TicksLeft)
			if ttl == 0 { // Destroy entity
				err := e.entity.AcceptRequest(entity.NewDeleteRequest(currentTick, ent.Id))
				if err != nil {
					// This shouldn't fail
					panic(err)
				}
			} else {
				ent.Attributes[game.TicksLeftAttr] = ttl - 1
			}
		}
	}

	// Cleanup
	e.entity.Cleanup(currentTick)
	e.action.Cleanup(currentTick)

	// DEBUG: print all entites
	log.Printf("Tick %v finished, entities:\n", currentTick)
	for _, ent := range e.entity.List() {
		log.Printf("entity=%+v position=%+v speed=%+v", ent, ent.Position, ent.Speed)
	}
}