diff --git a/client_conn.go b/client_conn.go index 8144ed8..9df2a9a 100644 --- a/client_conn.go +++ b/client_conn.go @@ -113,6 +113,9 @@ func handleClt(cc *ClientConn) { if errors.Is(cc.WhyClosed(), rudp.ErrTimedOut) { cc.Log("<->", "timeout") } else { + handlePlayerLeave(cc, &Leave{ + Type: Exit, + }) cc.Log("<->", "disconnect") } diff --git a/content.go b/content.go index c8e3fe6..532fc06 100644 --- a/content.go +++ b/content.go @@ -431,6 +431,7 @@ func muxNodeDefs(conns []*contentConn) (nodeDefs []mt.NodeDef, p0Map param0Map, } def.Param0 = param0 + oldName := def.Name // copy string to use later prepend(cc.mediaPool, &def.Name) prepend(cc.mediaPool, &def.Mesh) for i := range def.Tiles { @@ -456,6 +457,9 @@ func muxNodeDefs(conns []*contentConn) (nodeDefs []mt.NodeDef, p0Map param0Map, if param0 >= mt.Unknown && param0 <= mt.Ignore { param0 = mt.Ignore + 1 } + + // add nodeid (if reqested) + addNodeId(oldName, def.Param0) } } diff --git a/hop.go b/hop.go index ffff511..e9d3c32 100644 --- a/hop.go +++ b/hop.go @@ -17,6 +17,8 @@ func (cc *ClientConn) Hop(serverName string) error { cc.Log("<->", "hop", serverName) + source := cc.ServerName() + if cc.server() == nil { err := fmt.Errorf("no server connection") cc.Log("<->", err) @@ -160,5 +162,7 @@ func (cc *ClientConn) Hop(serverName string) error { return authIface.SetLastSrv(cc.Name(), serverName) } + handlePlayerHop(cc, source, serverName) + return nil } diff --git a/moderation.go b/moderation.go index 719606c..3d995c1 100644 --- a/moderation.go +++ b/moderation.go @@ -10,9 +10,16 @@ import ( // and closes the ClientConn. func (cc *ClientConn) Kick(reason string) { go func() { - ack, _ := cc.SendCmd(&mt.ToCltKick{ + kick := &mt.ToCltKick{ Reason: mt.Custom, Custom: reason, + } + + ack, _ := cc.SendCmd(kick) + + handlePlayerLeave(cc ,&Leave{ + Type: Kick, + Kick: kick, }) select { diff --git a/plugin_AO.go b/plugin_AO.go new file mode 100644 index 0000000..98f20b4 --- /dev/null +++ b/plugin_AO.go @@ -0,0 +1,251 @@ +package proxy + +import ( + "github.com/anon55555/mt" + "sync" +) + +type AOHandler struct { + AOIDs map[string]map[mt.AOID]bool + + OnAOMsg func(*ClientConn, mt.AOID, mt.AOMsg) bool + OnAOAdd func(*ClientConn, mt.AOID, *mt.AOAdd) bool + OnAORm func(*ClientConn, mt.AOID) bool +} + +// TODO: +// var aOCache map[mt.AOID]*mt.AOProps +// var aOCacheMu sync.RWMutex + +var aOHandlers []*AOHandler +var aOHandlersMu sync.RWMutex + +func handleAOAdd(sc *ServerConn, id mt.AOID, msg *mt.AOAdd) bool { + var handled bool + + for _, handler := range aOHandlers { + if handler.OnAOAdd == nil { + continue + } + if handler.AOIDs == nil && handler.OnAOAdd(sc.clt, id, msg) { + handled = true + } else if handler.AOIDs[sc.name][id] && handler.OnAOAdd(sc.clt, id, msg) { + handled = true + } + } + + return handled +} + +func handleAORm(sc *ServerConn, id mt.AOID) bool { + var handled bool + + for _, handler := range aOHandlers { + if handler.OnAORm == nil { + continue + } + if handler.AOIDs == nil && handler.OnAORm(sc.clt, id) { + handled = true + } else if handler.AOIDs[sc.name][id] && handler.OnAORm(sc.clt, id) { + handled = true + } + } + + return handled +} + +func handleAOMsg(sc *ServerConn, id mt.AOID, msg mt.AOMsg) bool { + var handled bool + + for _, handler := range aOHandlers { + if handler.OnAOMsg == nil { + continue + } + if handler.AOIDs == nil && handler.OnAOMsg(sc.clt, id, msg) { + handled = true + } else if handler.AOIDs[sc.name][id] && handler.OnAOMsg(sc.clt, id, msg) { + handled = true + } + } + + return handled +} + +func RegisterAOHandler(h *AOHandler) { + aOHandlersMu.Lock() + defer aOHandlersMu.Unlock() + + aOHandlers = append(aOHandlers, h) +} + +/// +/// - AOID managment +/// + +var LowestAOID mt.AOID = 0xA000 + +/// +/// - Global AOIDs +/// + +type globalAOID struct { + used, client, server, global bool +} + +var globalAOIDs = make(map[mt.AOID]globalAOID) +var globalAOIDsMu sync.RWMutex + +// GetGlobalAOID returns a globally unused AOID +func GetGlobalAOID() (bool, mt.AOID) { + globalAOIDsMu.Lock() + defer globalAOIDsMu.Unlock() + + notFound := true + var id mt.AOID = 0xFFFF + for notFound { + if globalAOIDs[id].used == false { + notFound = false + + globalAOIDs[id] = globalAOID{used: true, global: true} + } else if id < LowestAOID { + return false, 0xFFFF + } else { + id-- + } + } + + return !notFound, id +} + +// FreeAOID marks a id as globally unused +func FreeGlobalAOID(id mt.AOID, srv string) { + globalAOIDsMu.Lock() + defer globalAOIDsMu.Unlock() + + delete(globalAOIDs, id) +} + +/// +/// - Server AOIDs +/// + +type sAOID map[mt.AOID]bool + +var serverAOIDs = make(map[string]sAOID) +var serverAOIDsMu sync.RWMutex + +func (sm sAOID) empty() bool { + for _, v := range sm { + if v { + return false + } + } + + return true +} + +// GetServerAOId returns a free mt.AOID for a server, which will be marked as used +func GetServerAOId(srv string) (bool, mt.AOID) { + globalAOIDsMu.Lock() + defer globalAOIDsMu.Unlock() + + serverAOIDsMu.Lock() + defer serverAOIDsMu.Unlock() + + notFound := true + var id mt.AOID = 0xFFFF + for notFound { + if globalAOIDs[id].used == false || (globalAOIDs[id].server == true && !serverAOIDs[srv][id]) { + notFound = false + if serverAOIDs[srv] == nil { + serverAOIDs[srv] = make(map[mt.AOID]bool) + } + serverAOIDs[srv][id] = true + globalAOIDs[id] = globalAOID{used: true, server: true} + } else if id < LowestAOID { + return false, 0xFFFF + } else { + id-- + } + } + + return !notFound, id +} + +// FreeServerAOID frees a server AOID +func FreeServerAOID(srv string, id mt.AOID) { + globalAOIDsMu.Lock() + defer globalAOIDsMu.Unlock() + + serverAOIDsMu.Lock() + defer serverAOIDsMu.Unlock() + + delete(serverAOIDs[srv], id) + if serverAOIDs[srv].empty() { + delete(globalAOIDs, id) + } +} + +/// +/// - clientbound AOIDs +/// + +type cAOID map[mt.AOID]bool + +var clientAOIDs = make(map[string]cAOID) +var clientAOIDsMu sync.RWMutex + +func (ca cAOID) empty() bool { + for _, v := range ca { + if v { + return false + } + } + + return true +} + +// GetFreeAOID returns the next free AOID for a client +func (cc *ClientConn) GetFreeAOID() (bool, mt.AOID) { + globalAOIDsMu.Lock() + defer globalAOIDsMu.Unlock() + + clientAOIDsMu.Lock() + defer clientAOIDsMu.Unlock() + + name := cc.Name() + + notFound := true + var id mt.AOID = 0xFFFF + for notFound { + if globalAOIDs[id].used == false || (globalAOIDs[id].client == true && !clientAOIDs[name][id]) { + notFound = false + if clientAOIDs[name] == nil { + clientAOIDs[name] = make(map[mt.AOID]bool) + } + clientAOIDs[name][id] = true + globalAOIDs[id] = globalAOID{used: true, client: true} + } else if id < LowestAOID { + return false, 0xFFFF + } else { + id-- + } + } + + return !notFound, id +} + +// FreeAOID marks AOID as free +func (cc *ClientConn) FreeAOID(id mt.AOID) { + globalAOIDsMu.Lock() + defer globalAOIDsMu.Unlock() + + clientAOIDsMu.Lock() + defer clientAOIDsMu.Unlock() + + name := cc.Name() + delete(clientAOIDs[name], id) + if clientAOIDs[name].empty() { + delete(globalAOIDs, id) + } +} diff --git a/plugin_map.go b/plugin_map.go new file mode 100644 index 0000000..23ca81e --- /dev/null +++ b/plugin_map.go @@ -0,0 +1,76 @@ +package proxy + +import ( + "sync" + + "github.com/anon55555/mt" +) + +type BlkDataHandler struct { + UsePos bool // if set, filters by BlkPos in Pos + Pos [3]int16 // ^ specifies BlkPos when UsePos is set + Handler func(*ClientConn, *mt.ToCltBlkData) bool +} + +var blkDataHandlers []BlkDataHandler +var blkDataHandlersMu sync.RWMutex + +var cacheNodes = map[string]map[mt.Content]bool{} +var cacheNodesMu sync.RWMutex + +// RegisterCacheNode tells server, that nodename is suppost to be cached +func RegisterCacheNode(nodename string) { + cacheNodesMu.Lock() + defer cacheNodesMu.Unlock() + + if cacheNodes[nodename] == nil { + cacheNodes[nodename] = map[mt.Content]bool{} // default value, empty map + } +} + +// GetNodeId gets the nodeid of a +// If not registerd returns map[mt.Content]bool{} +func GetNodeId(nodename string) map[mt.Content]bool { + cacheNodesMu.RLock() + defer cacheNodesMu.RUnlock() + + if cacheNodes[nodename] != nil { + return cacheNodes[nodename] + } else { + return nil + } +} + +// addNodeId sets node id, if allready set, ignore +func addNodeId(nodename string, id mt.Content) { + cacheNodesMu.Lock() + defer cacheNodesMu.Unlock() + + if cacheNodes[nodename] != nil { + cacheNodes[nodename][id] = true + } +} + +// RegisterBlkDataHandler registers a BlkDataHande +func RegisterBlkDataHandler(handler BlkDataHandler) { + blkDataHandlersMu.Lock() + defer blkDataHandlersMu.Unlock() + + blkDataHandlers = append(blkDataHandlers, handler) +} + +func handleBlkData(cc *ClientConn, cmd *mt.ToCltBlkData) bool { + blkDataHandlersMu.RLock() + defer blkDataHandlersMu.RUnlock() + + handled := false + for _, handler := range blkDataHandlers { + if !handler.UsePos && handler.Handler(cc, cmd) { + handled = true + } else if handler.Pos == cmd.Blkpos && handler.Handler(cc, cmd) { + handled = true + } + } + + return handled +} diff --git a/plugin_node.go b/plugin_node.go new file mode 100644 index 0000000..e54d017 --- /dev/null +++ b/plugin_node.go @@ -0,0 +1,176 @@ +package proxy + +import ( + "sync" + + "github.com/anon55555/mt" +) + +type NodeHandler struct { + Node string + nodeIds map[mt.Content]bool + OnDig func(*ClientConn, *mt.ToSrvInteract) bool + OnStopDigging func(*ClientConn, *mt.ToSrvInteract) bool + OnDug func(*ClientConn, *mt.ToSrvInteract) bool + OnPlace func(*ClientConn, *mt.ToSrvInteract) bool // TODO IMPLEMENTED +} + +var nodeHandlers []*NodeHandler +var nodeHandlersMu sync.RWMutex + +var mapCache map[[3]int16]*[4096]mt.Content +var mapCacheMu sync.RWMutex +var mapCacheOnce sync.Once + +func initMapCache() { + mapCacheOnce.Do(func() { + mapCache = map[[3]int16]*[4096]mt.Content{} + }) +} + +func RegisterNodeHandler(handler *NodeHandler) { + nodeHandlersMu.Lock() + defer nodeHandlersMu.Unlock() + + RegisterCacheNode(handler.Node) + nodeHandlers = append(nodeHandlers, handler) +} + +func initNodeHandlerNodeIds() { + nodeHandlersMu.RLock() + defer nodeHandlersMu.RUnlock() + + for _, h := range nodeHandlers { + if h.nodeIds == nil { + id := GetNodeId(h.Node) + + if id != nil { + h.nodeIds = id + } + } + } +} + +func GetMapCache() map[[3]int16]*[4096]mt.Content { + initMapCache() + + mapCacheMu.RLock() + defer mapCacheMu.RUnlock() + + return mapCache +} + +func IsCached(pos [3]int16) bool { + initMapCache() + + mapCacheMu.RLock() + defer mapCacheMu.RUnlock() + + blkpos, i := mt.Pos2Blkpos(pos) + if mapCache[blkpos] == nil { + return false + } else { + return mapCache[blkpos][i] != 0 + } +} + +func handleNodeInteraction(cc *ClientConn, pointedNode *mt.PointedNode, cmd *mt.ToSrvInteract) bool { + nodeHandlersMu.RLock() + defer nodeHandlersMu.RUnlock() + + mapCacheMu.RLock() + defer mapCacheMu.RUnlock() + + var handled bool + for _, handler := range nodeHandlers { + // check if nodeId is right + pos, i := mt.Pos2Blkpos(pointedNode.Under) + if handler.nodeIds[mapCache[pos][i]] { + var h bool + + switch cmd.Action { + case mt.Dig: + if handler.OnDig != nil { + h = handler.OnDig(cc, cmd) + } + case mt.StopDigging: + if handler.OnStopDigging != nil { + h = handler.OnStopDigging(cc, cmd) + } + case mt.Dug: + if handler.OnDug != nil { + h = handler.OnDug(cc, cmd) + } + case mt.Place: + if handler.OnPlace != nil { + h = handler.OnPlace(cc, cmd) + } + } + + if h { + handled = h + } + } + } + + return handled +} + +func initPluginNode() { + RegisterBlkDataHandler(BlkDataHandler{ + Handler: func(cc *ClientConn, cmd *mt.ToCltBlkData) bool { + initMapCache() + initNodeHandlerNodeIds() + + mapCacheMu.Lock() + defer mapCacheMu.Unlock() + + for i, node := range cmd.Blk.Param0 { + // check if node is interesting + interesting := false + for _, h := range nodeHandlers { + if h.nodeIds[node] { + interesting = true + break + } + } + + // if it changed + if !interesting { + if mapCache[cmd.Blkpos] != nil { + if mapCache[cmd.Blkpos][i] != 0 && mapCache[cmd.Blkpos][i] != node { + interesting = true + } + } + } + + if interesting { + if mapCache[cmd.Blkpos] == nil { + mapCache[cmd.Blkpos] = &[4096]mt.Content{} + } + mapCache[cmd.Blkpos][i] = node + } + } + + return false + }, + }) + + RegisterInteractionHandler(InteractionHandler{ + Type: AnyInteraction, + Handler: func(cc *ClientConn, cmd *mt.ToSrvInteract) bool { + handled := false + + if pointedNode, ok := cmd.Pointed.(*mt.PointedNode); ok { + if IsCached(pointedNode.Under) { + // is a interesting node + if handleNodeInteraction(cc, pointedNode, cmd) { + handled = true + } + } + } + + return handled + }, + }) +} diff --git a/plugin_player.go b/plugin_player.go new file mode 100644 index 0000000..473675a --- /dev/null +++ b/plugin_player.go @@ -0,0 +1,73 @@ +package proxy + +import ( + "github.com/anon55555/mt" + "sync" +) + +type LeaveType uint8 + +const ( + Exit LeaveType = iota + Kick +) + +type Leave struct{ + Type LeaveType + Kick *mt.ToCltKick +} + +type PlayerHandler struct{ + Join func(cc *ClientConn) (destination string) + Leave func(cc *ClientConn, l *Leave) + Hop func(cc *ClientConn, source, destination string) +} + +var playerHandlers []*PlayerHandler +var playerHandlersMu sync.RWMutex + +func RegisterPlayerHandler(h *PlayerHandler) { + playerHandlersMu.Lock() + defer playerHandlersMu.Unlock() + + playerHandlers = append(playerHandlers, h) +} + +func handlePlayerJoin(cc *ClientConn) string { + playerHandlersMu.RLock() + defer playerHandlersMu.RUnlock() + + var dest string + + for _, handler := range playerHandlers { + if handler.Join != nil { + if d := handler.Join(cc); d != "" { + dest = d + } + } + } + + return dest +} + +func handlePlayerLeave(cc *ClientConn, l *Leave) { + playerHandlersMu.RLock() + defer playerHandlersMu.RUnlock() + + for _, handler := range playerHandlers { + if handler.Leave != nil { + handler.Leave(cc, l) + } + } +} + +func handlePlayerHop(cc *ClientConn, source, leave string) { + playerHandlersMu.RLock() + defer playerHandlersMu.RUnlock() + + for _, handler := range playerHandlers { + if handler.Hop != nil { + handler.Hop(cc, source, leave) + } + } +} diff --git a/process.go b/process.go index ed8004d..adb1ea8 100644 --- a/process.go +++ b/process.go @@ -662,11 +662,19 @@ func (sc *ServerConn) process(pkt mt.Pkt) { for k := range cmd.Msgs { sc.swapAOID(&cmd.Msgs[k].ID) sc.handleAOMsg(cmd.Msgs[k].Msg) + + if handleAOMsg(sc, cmd.Msgs[k].ID, cmd.Msgs[k].Msg) { + return + } } case *mt.ToCltAORmAdd: resp := &mt.ToCltAORmAdd{} for _, ao := range cmd.Remove { + if handleAORm(sc, ao) { + continue + } + delete(sc.aos, ao) resp.Remove = append(resp.Remove, ao) } @@ -697,11 +705,19 @@ func (sc *ServerConn) process(pkt mt.Pkt) { sc.swapAOID(&ao.ID) for _, msg := range ao.InitData.Msgs { sc.handleAOMsg(msg) + + if handleAOMsg(sc, ao.ID, msg) { + return + } } resp.Add = append(resp.Add, ao) sc.aos[ao.ID] = struct{}{} } + + if handleAOAdd(sc, ao.ID, &ao) { + continue + } } clt.SendCmd(resp) @@ -807,6 +823,10 @@ func (sc *ServerConn) process(pkt mt.Pkt) { } sc.prependInv(cmd.Blk.NodeMetas[k].Inv) } + + if handleBlkData(sc.client(), cmd) { + return + } case *mt.ToCltAddNode: sc.globalParam0(&cmd.Node.Param0) case *mt.ToCltAddParticleSpawner: diff --git a/run.go b/run.go index 451b531..d4611ec 100644 --- a/run.go +++ b/run.go @@ -54,6 +54,9 @@ func runFunc() { log.Println("listen", l.Addr()) + // plugin_node.go + initPluginNode() + go func() { sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) @@ -107,7 +110,21 @@ func runFunc() { return } - srvName, srv := conf.DefaultServerInfo() + var srv, s Server + var found bool + + srvName := handlePlayerJoin(cc) + if srvName != "" { + s, found = Conf().Servers[srvName] + } + + if found { + srv = s + } else { + srvName, srv = conf.DefaultServerInfo() + } + + lastSrv, err := authIface.LastSrv(cc.Name()) if err == nil && !Conf().ForceDefaultSrv && lastSrv != srvName { for name, s := range conf.Servers {