diff --git a/internal/app/ui.go b/internal/app/ui.go index b37cfed..e916188 100644 --- a/internal/app/ui.go +++ b/internal/app/ui.go @@ -139,6 +139,9 @@ func (ui *UI) handleNotification(v wire.Value) bool { if ui.room == nil { ui.room = new(wire.Room) } + + ui.room.Name = n.RoomName + ui.room.Rect = n.RoomSize ui.room.Entities = n.Entities return true diff --git a/internal/sim/door.go b/internal/sim/door.go index 709ecc9..64b17f3 100644 --- a/internal/sim/door.go +++ b/internal/sim/door.go @@ -8,22 +8,96 @@ import ( type door struct { *blammo.Log - to string - exit int + to string + exit int + arrived int } func (d *door) update(time.Duration) {} +func (d *door) exec(w *world, r *room, p *player, seq int) result { + p.Info("executing door to %q for player %s", d.to, p.name) + + dest, ok := w.rooms[d.to] + if !ok { + p.Error("door destination %q does not exist", d.to) + return result{} + } + p.Info("found destination room %q", d.to) + + exit := dest.findEntity(d.exit) + if exit == nil { + p.Error("door exit %d does not exist", d.exit) + return result{} + } + + exitDoor, ok := exit.behavior.(*door) + if !ok { + p.Error("exit entity %d is not a door", d.exit) + return result{} + } + p.Info("found exit door %v", exitDoor) + + t := dest.getTile(exit.Position) + p.Info("exit tile: %v", t) + if t.isOccupied() { + p.Error("destination tile %v is occupied", t) + return result{} + } + + p.Info("removing player from room %s", r.name) + r.removePlayer(p.name) + p.Info("adding player to room %s", dest.name) + if t.addEntity(p.avatar) { + p.Info("added player avatar to tile %v", t) + exitDoor.arrived = p.avatar.ID + p.avatar.Position = exit.Position + } else { + p.Error("failed to add player avatar to tile %v", t) + } + dest.addPlayer(p) + return result{} +} + func (d *door) onStartOverlap(e *entity) { - d.Info("start overlap: %v", e) + if e.ID == d.arrived { + return + } + if p, ok := e.behavior.(*player); ok { + d.Info("player %s start overlap on door to %s", p.name, d.to) + if p.pending != nil { + d.Info("player %s starting overlap on door to %s has a pending request", p.name, d.to) + } else { + d.Info("player %s starting overlap on door to %s has NO pending request", p.name, d.to) + p.pending = &Request{Wants: d} + } + } } func (d *door) onOverlap(e *entity) { - d.Info("overlap: %v", e) + if e.ID == d.arrived { + return + } + // d.Info("overlap: %v", e) + if p, ok := e.behavior.(*player); ok { + d.Info("player %s is continuing overlap on door to %s", p.name, d.to) + if p.pending != nil { + d.Info("player %s continuing to overlap door to %s has a pending request", p.name, d.to) + } else { + d.Info("player %s continuing to overlap door to %s has NO pending request", p.name, d.to) + p.pending = &Request{Wants: d} + } + } } func (d *door) onStopOverlap(e *entity) { - d.Info("stop overlap: %v", e) + if e.ID == d.arrived { + d.arrived = 0 + } + // d.Info("stop overlap: %v", e) + if p, ok := e.behavior.(*player); ok { + d.Info("player %s stepped off of door to %s", p.name, d.to) + } } /* diff --git a/internal/sim/effect.go b/internal/sim/effect.go index d0997a5..8fb049a 100644 --- a/internal/sim/effect.go +++ b/internal/sim/effect.go @@ -6,6 +6,10 @@ type Effect interface { exec(*world, *room, *player, int) result } +type effect func(*world, *room, *player, int) result + +func (f effect) exec(w *world, r *room, p *player, seq int) result { return f(w, r, p, seq) } + type result struct { reply wire.Value } diff --git a/internal/sim/player.go b/internal/sim/player.go index ed5ec80..59479cf 100644 --- a/internal/sim/player.go +++ b/internal/sim/player.go @@ -97,6 +97,9 @@ func (p *player) runLoop(conn *websocket.Conn) { for { select { case res := <-p.outbox: + if err, ok := res.Body.(error); ok { + p.Error("sending error: %s", err) + } if err := sendResponse(conn, res); err != nil { p.Error(err.Error()) } @@ -152,6 +155,8 @@ func sendResponse(conn *websocket.Conn, res wire.Response) error { return nil } +func (p *player) update(dt time.Duration) {} + type spawnPlayer struct{} func (s spawnPlayer) exec(w *world, r *room, p *player, seq int) result { @@ -159,14 +164,16 @@ func (s spawnPlayer) exec(w *world, r *room, p *player, seq int) result { ID: <-w.nextID, Glyph: '@', solid: true, - behavior: doNothing{}, + behavior: p, } p.avatar = &e - for n, t := range r.tiles { + for n, _ := range r.tiles { + t := &r.tiles[n] x, y := n%r.Width, n/r.Width e.Position = math.Vec{x, y} if t.addEntity(&e) { + p.Info("player added to tile at %s", e.Position) return result{} } } @@ -182,15 +189,26 @@ func (m *Move) exec(w *world, r *room, p *player, seq int) result { target := pos.Add(math.Vec(*m)) p.Info("running move for player %s from %v to %v", p.name, p.avatar.Position, target) if !r.Contains(target) { + p.Error("target cell (%d, %d) is out of bounds", target.X, target.Y) return result{reply: wire.Errorf("target cell (%d, %d) is out of bounds", target.X, target.Y)} } currentTile := r.getTile(pos) + if !currentTile.hasEntity(p.avatar.ID) { + p.Error("player cannot move off of %s because they were not actually there", pos) + p.Error("tile %d: %v", pos, currentTile) + return result{reply: wire.Errorf("player cannot move off of %s because they were not actually there", pos)} + } + nextTile := r.getTile(target) - if !nextTile.addEntity(p.avatar) { + if nextTile.isOccupied() { + p.Error("target cell (%d, %d) is occupied", target.X, target.Y) return result{reply: wire.Errorf("target cell (%d, %d) is occupied", target.X, target.Y)} } + currentTile.removeEntity(p.avatar.ID) + nextTile.addEntity(p.avatar) + p.avatar.Position = target return result{reply: wire.OK{}} } diff --git a/internal/sim/room.go b/internal/sim/room.go index cbf7373..3fc0e50 100644 --- a/internal/sim/room.go +++ b/internal/sim/room.go @@ -36,6 +36,17 @@ func (r *room) playerAvatars() map[string]int { return all } +func (r *room) findEntity(id int) *entity { + for _, t := range r.tiles { + for _, e := range t.here { + if e.ID == id { + return e + } + } + } + return nil +} + func (r *room) addEntity(e *entity) bool { t := r.getTile(e.Position) if t == nil { @@ -49,8 +60,12 @@ func (r *room) addPlayer(p *player) { } func (r *room) removePlayer(name string) bool { - if _, ok := r.players[name]; ok { + if p, ok := r.players[name]; ok { delete(r.players, name) + t := r.getTile(p.avatar.Position) + if t != nil { + t.removeEntity(p.avatar.ID) + } return true } return false diff --git a/internal/sim/tile.go b/internal/sim/tile.go index 2ca4ebf..872951c 100644 --- a/internal/sim/tile.go +++ b/internal/sim/tile.go @@ -1,6 +1,7 @@ package sim import ( + "fmt" "time" ) @@ -9,6 +10,14 @@ type tile struct { here []*entity } +func (t tile) String() string { + ids := make([]int, len(t.here)) + for i, e := range t.here { + ids[i] = e.ID + } + return fmt.Sprintf("{%c %v}", t.floor, ids) +} + func (t *tile) addEntity(e *entity) bool { if e.solid { for _, other := range t.here { @@ -21,7 +30,8 @@ func (t *tile) addEntity(e *entity) bool { return true } -func (t *tile) removeEntity(id int) { +func (t *tile) removeEntity(id int) bool { + start := len(t.here) here := t.here[:0] for _, e := range t.here { if e.ID != id { @@ -29,6 +39,25 @@ func (t *tile) removeEntity(id int) { } } t.here = here + return len(t.here) != start +} + +func (t *tile) hasEntity(id int) bool { + for _, e := range t.here { + if e.ID == id { + return true + } + } + return false +} + +func (t *tile) isOccupied() bool { + for _, e := range t.here { + if e.solid { + return true + } + } + return false } func (t *tile) update(d time.Duration) { diff --git a/internal/sim/world.go b/internal/sim/world.go index 014ed18..d5a1f81 100644 --- a/internal/sim/world.go +++ b/internal/sim/world.go @@ -247,6 +247,8 @@ func (w *world) tick(d time.Duration) { // send frame data to all players for _, r := range w.rooms { frame := wire.Frame{ + RoomName: r.name, + RoomSize: r.Rect, Entities: r.allEntities(), Players: r.playerAvatars(), } diff --git a/internal/wire/frame.go b/internal/wire/frame.go index f567121..4a8cfd1 100644 --- a/internal/wire/frame.go +++ b/internal/wire/frame.go @@ -1,6 +1,10 @@ package wire +import "github.com/jordanorelli/astro-domu/internal/math" + type Frame struct { + RoomName string `json:"room_name"` + RoomSize math.Rect `json:"room_size"` Entities map[int]Entity `json:"entities"` Players map[string]int `json:"players"` }