// counter.go: la contabilidad de muertes. // // A proposito NO tiene nada especifico de Windows: toda la lectura de // memoria vive en main.go. Asi esta parte, que es la que concentra las // reglas (a que personaje va cada muerte, que pasa al cambiar de // partida, que se muestra con el juego cerrado), se puede compilar y // testear en cualquier plataforma. Las tres veces que algo conto mal, el // error estuvo aca. package main import ( "log" "strings" "sync" ) // El total que se muestra ES el que lleva el juego para el personaje // cargado. No hay ajustes ni acumuladores propios: al cambiar de // personaje el numero salta al suyo en la misma lectura. type state struct { mu sync.Mutex connected bool playerLoaded bool rawDeaths int32 haveReading bool bossFight bool charName string charKey string slot int total int64 announced bool lastErr string } type stateSnapshot struct { RawDeaths int32 Slot int Total int64 BossFight bool Connected bool PlayerLoaded bool CharName string } var st state var totals *totalsStore // init arranca mostrando el ultimo personaje conocido, para que el // overlay tenga un numero con sentido antes de que abras el juego. func (s *state) init(store *totalsStore) { key, name, total := store.lastKnownTotal() s.mu.Lock() s.charName = name s.charKey = key s.slot = -1 s.total = total s.mu.Unlock() if name != "" { log.Printf("ultimo personaje conocido: %s con %d muertes", name, total) } } func (s *state) snapshot() stateSnapshot { s.mu.Lock() defer s.mu.Unlock() total := s.total if s.haveReading { total = int64(s.rawDeaths) } return stateSnapshot{ RawDeaths: s.rawDeaths, Slot: s.slot, Total: total, BossFight: s.bossFight, Connected: s.connected, PlayerLoaded: s.playerLoaded, CharName: s.charName, } } func (s *state) name() string { s.mu.Lock() defer s.mu.Unlock() return s.charName } // nombreMostrado: lo que diga el config manda; si esta vacio usamos el // nombre del personaje que leemos del juego. func nombreMostrado(cfg config) string { if n := strings.TrimSpace(cfg.Name); n != "" { return n } if n := st.name(); n != "" { return n } // Vacio a proposito: que la etiqueta la elija la interfaz, que es la // que sabe si esta mostrando un jugador o dos. return "" } // setCharacter se llama cuando cambia el personaje cargado: el total // pasa a ser el que el juego lleva para ese personaje. func (s *state) setCharacter(slot int, name string, raw int32) { key := characterKey(slot, name) if key == "" { return // ni slot ni nombre: no sabemos a quien imputarle esto } s.mu.Lock() if key == s.charKey && s.haveReading { // Mismo personaje: el nombre puede haber aparecido recien, porque // el slot se lee antes que el nombre. if name != "" && name != s.charName { s.charName = name total := s.total s.mu.Unlock() totals.save(key, charTotal{Name: name, Total: total}) return } s.mu.Unlock() return } s.charKey = key s.slot = slot if name != "" { s.charName = name } name = s.charName // Adoptamos ya la lectura del personaje nuevo: si no, el snapshot // seguiria mostrando las muertes del personaje ANTERIOR hasta la // siguiente muerte. s.rawDeaths = raw s.haveReading = true s.total = int64(raw) total := s.total s.mu.Unlock() log.Printf("personaje: %s (slot %d) -> %d muertes", name, slot, total) totals.save(key, charTotal{Name: name, Total: total}) } func (s *state) setReading(raw int32, boss bool) { s.mu.Lock() s.connected = true s.playerLoaded = true s.announced = false changed := !s.haveReading || s.rawDeaths != raw s.haveReading = true s.rawDeaths = raw s.bossFight = boss s.lastErr = "" total := int64(raw) grew := total > s.total s.total = total key, name := s.charKey, s.charName s.mu.Unlock() if !changed { return } if grew { log.Printf("nueva muerte de %s -> %d", name, total) } totals.save(key, charTotal{Name: name, Total: total}) } // setPlayerUnloaded: el juego sigue abierto pero no hay personaje en el // mundo (menu principal o pantalla de carga). A diferencia de una // desconexion, NO apagamos connected: el overlay tiene que seguir // mostrando el total y no un guion, porque si no parpadearia en cada // pantalla de carga (o sea, en cada muerte). func (s *state) setPlayerUnloaded(reason string) { s.mu.Lock() was := s.playerLoaded first := !s.announced s.announced = true s.connected = true s.playerLoaded = false s.bossFight = false s.lastErr = reason s.mu.Unlock() if was || first { log.Printf("sin personaje cargado (%s) - el total queda congelado hasta que vuelvas al mundo", reason) } } func (s *state) setDisconnected(reason string) { s.mu.Lock() wasConnected := s.connected s.connected = false s.playerLoaded = false s.bossFight = false s.lastErr = reason s.mu.Unlock() if wasConnected { log.Printf("se perdio la conexion con el juego: %s", reason) } }