go-susapp/sus.go

296 lines
9.2 KiB
Go
Raw Normal View History

2021-09-12 00:25:10 +01:00
package main
import (
"fmt"
"log"
"net"
"os"
"time"
"github.com/gotk3/gotk3/cairo"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
"codehub.onpointcoding.net/sean/go-susapp/enum"
"codehub.onpointcoding.net/sean/go-susapp/gamedata"
"codehub.onpointcoding.net/sean/go-susapp/packets"
"codehub.onpointcoding.net/sean/go-susapp/protocol"
"codehub.onpointcoding.net/sean/go-susapp/util"
)
const appID = "net.onpointcoding.susapp"
func main() {
fmt.Println("Booting Sus App")
sus := &SusApp{}
sus.startApp()
fmt.Println("Goodbye :(")
}
type SusApp struct {
state *State
application *gtk.Application
appWindow *gtk.ApplicationWindow
renderer *Renderer
keyInput *KeyInputHandler
mouseInput *MouseInputHandler
packetProcessor *PacketProcessor
movementProcessor *MovementProcessor
networkHandler *protocol.PacketHandler
}
func (sus *SusApp) startApp() {
// Create Gtk Application, change appID to your application domain name reversed.
var err error
sus.application, err = gtk.ApplicationNew(appID, glib.APPLICATION_NON_UNIQUE)
// Check to make sure no errors when creating Gtk Application
if err != nil {
log.Fatal("Could not create application.", err)
}
renderTicker := time.NewTicker(50 * time.Millisecond)
renderTickerDone := make(chan bool)
defer func() {
fmt.Println("Stopping render ticks")
renderTicker.Stop()
renderTickerDone <- true
}()
movementTicker := time.NewTicker(50 * time.Millisecond)
movementTickerDone := make(chan bool)
defer func() {
fmt.Println("Stopping movement ticks")
movementTicker.Stop()
movementTickerDone <- true
}()
sus.state = CreateAppState(GenerateVersionNumber(2021, 6, 30, 0))
if sus.state == nil {
fmt.Println("Error loading new app state")
return
}
sus.renderer = NewRenderer(sus.state)
sus.keyInput = NewKeyInputHandler(sus.state)
sus.mouseInput = NewMouseInputHandler(sus.state, sus.renderer, sus.playGame, sus.openSettings, sus.disconnectFromGame)
sus.packetProcessor = NewPacketProcessor(sus)
sus.movementProcessor = &MovementProcessor{sus: sus}
fmt.Printf("Starting Sus App %s\n", GetVersionFormatted(sus.state.Version))
sus.application.Connect("activate", func() {
w := 800
h := 450
// Create ApplicationWindow
sus.appWindow, err = gtk.ApplicationWindowNew(sus.application)
if err != nil {
log.Fatal("Could not create application window.", err)
}
// Set ApplicationWindow Properties
sus.appWindow.SetTitle("Sus App")
sus.appWindow.SetDefaultSize(w, h)
layout, _ := gtk.LayoutNew(nil, nil)
sus.appWindow.Add(layout)
da, _ := gtk.DrawingAreaNew()
da.SetHAlign(gtk.ALIGN_FILL)
da.SetVAlign(gtk.ALIGN_FILL)
da.SetHExpand(true)
da.SetVExpand(true)
da.SetSizeRequest(w, h)
layout.Add(da)
// Listen for ticks from renderTicker
go func() {
for {
select {
case <-renderTickerDone:
return
case <-renderTicker.C:
da.QueueDraw()
}
}
}()
// Listen for ticks from movementTicker
go func() {
z := time.Now().UnixMilli()
for {
select {
case <-movementTickerDone:
return
case a := <-movementTicker.C:
y := a.UnixMilli() - z
z = a.UnixMilli()
sus.movementProcessor.Tick(y)
}
}
}()
r := sus.renderer
da.Connect("draw", func(da *gtk.DrawingArea, cr *cairo.Context) {
r.Draw(da, cr)
})
da.AddEvents(int(gdk.KEY_RELEASE_MASK) | int(gdk.KEY_PRESS_MASK) | int(gdk.EVENT_CONFIGURE) | int(gdk.EVENT_BUTTON_PRESS))
da.Connect("event", sus.mouseInput.motionEvent)
sus.appWindow.Connect("button-press-event", sus.mouseInput.buttonPressEvent)
sus.appWindow.Connect("key-press-event", sus.keyInput.keyPressEvent)
sus.appWindow.Connect("key-release-event", sus.keyInput.keyReleaseEvent)
sus.appWindow.Connect("configure-event", func(window *gtk.ApplicationWindow, ev *gdk.Event) {
e := gdk.EventConfigureNewFromEvent(ev)
da.SetSizeRequest(e.Width(), e.Height())
})
sus.appWindow.Connect("destroy", func(window *gtk.ApplicationWindow) bool {
fmt.Println("Closing Sus App")
sus.disconnectFromGame()
sus.application.Quit()
return true
})
sus.appWindow.ShowAll()
})
sus.application.Run(os.Args)
}
func (sus *SusApp) setupPacketHandler(ip *net.IP, port int, firstPacket func(ph *protocol.PacketHandler)) {
addr := net.UDPAddr{
Port: port,
IP: *ip,
}
ser, err := net.DialUDP("udp", nil, &addr)
if err != nil {
fmt.Printf("Some error %v\n", err)
return
}
defer ser.Close()
fmt.Printf("Starting connection: %v => %v\n", ser.LocalAddr().String(), ser.RemoteAddr().String())
nethandler := protocol.NewPacketHandler(ser, &addr)
nethandler.HandleNormalPacket = sus.handleNormalPacket
nethandler.HandleReliablePacket = sus.handleReliablePacket
nethandler.HandleForceDisconnectPacket = sus.handleForceDisconnectPacket
nethandler.HandleNormalDisconnectPacket = sus.handleNormalDisconnectPacket
nethandler.HandleAcknowledgementPacket = sus.handleAcknowledgementPacket
nethandler.HandlePingPacket = sus.handlePingPacket
sus.networkHandler = nethandler
p := make([]byte, 4096)
firstPacket(nethandler)
for {
n, _, err := ser.ReadFromUDP(p)
if n != 0 {
if err != nil {
fmt.Printf("Some error %v\n", err)
continue
}
go nethandler.ReadPacket(p[:n])
}
}
}
func (sus *SusApp) playGame() {
sus.state.Screen = enum.SCREEN_WAITING
sus.state.IsTypingReady = false
addrstr := fmt.Sprintf("%v:%v", sus.state.Settings.ServerAddress, sus.state.Settings.ServerPort)
raddr, err := net.ResolveUDPAddr("udp", addrstr)
if err != nil {
fmt.Printf("Failed to resolve address: %v\n", addrstr)
return
}
go sus.setupPacketHandler(&raddr.IP, raddr.Port, func(ph *protocol.PacketHandler) {
fmt.Println("Sending first packet")
ph.SendHelloPacket(ph.GetNonce(), 0, sus.state.Version, sus.state.Settings.PlayerName, 0, sus.state.Settings.Language, sus.state.Settings.QuickChatMode)
y, _ := util.CodeToGameID(sus.state.TypedGameID)
var z protocol.HazelPayload = &packets.JoinGameC2S{GameID: y}
ph.SendReliablePacket(ph.GetNonce(), []*protocol.Hazel{
protocol.NewHazelFromPayload(&z),
})
})
}
func (sus *SusApp) openSettings() {
openSettingsWindow(sus.application, nil, sus.state)
}
func (sus *SusApp) disconnectFromGame() {
sus.state.IsLobbyMenuOpen = false
if sus.networkHandler != nil {
sus.networkHandler.SendNormalDisconnectPacket()
}
}
// TODO: implement all these functions
func (sus *SusApp) handleNormalPacket(net *protocol.PacketHandler, h []*protocol.Hazel) {
sus.packetProcessor.receiveNormalPacket(net, h)
}
func (sus *SusApp) handleReliablePacket(net *protocol.PacketHandler, nonce uint16, h []*protocol.Hazel) {
sus.packetProcessor.receiveReliablePacket(net, nonce, h)
}
func (sus *SusApp) handleForceDisconnectPacket(net *protocol.PacketHandler, h *protocol.Hazel) {
net.SendNormalDisconnectPacket()
p := h.GetUnderlyingPacket()
sus.state.DisconnectReason = p.Read()
if p.Remaining() > 0 {
sus.state.DisconnectStringReason = p.ReadString()
sus.state.DisconnectReason = 0xfe
}
sus.state.Screen = enum.SCREEN_ERROR
sus.state.IsLobbyMenuOpen = false
sus.state.DisconnectReason = 0xfe
sus.state.DisconnectStringReason = "This is a very long piece of text that must be completely read and understood thank you for reading :)"
}
func (sus *SusApp) handleNormalDisconnectPacket(net *protocol.PacketHandler) {
net.SendNormalDisconnectPacket()
sus.state.DisconnectReason = 0xff
sus.state.Screen = enum.SCREEN_TITLE
sus.state.IsLobbyMenuOpen = false
}
func (sus *SusApp) handleAcknowledgementPacket(net *protocol.PacketHandler, nonce uint16, missing byte) {
if sus.state.CurrentGame != nil && nonce != 0 {
if nonce == sus.state.CurrentGame.CheckNameNonce && sus.state.CurrentGame.CheckedNameAndColor&0b01 == 0 {
sus.state.CurrentGame.CheckedNameAndColor |= 0b01
if sus.state.CurrentGame.CheckedNameAndColor == 0b11 && sus.state.CurrentGame.CheckedNameAndColor&0b10 == 0 {
sus.sendPetHatAndSkin(net)
}
} else if nonce == sus.state.CurrentGame.CheckColorNonce {
sus.state.CurrentGame.CheckedNameAndColor |= 0b10
if sus.state.CurrentGame.CheckedNameAndColor == 0b11 {
sus.sendPetHatAndSkin(net)
}
}
}
}
func (sus *SusApp) handlePingPacket(net *protocol.PacketHandler, nonce uint16) {
// TODO: setup acknowledgement tracking and remove hard coded 0xff
net.SendAcknowledgementPacket(nonce, 0xff)
}
func (sus *SusApp) sendPetHatAndSkin(net *protocol.PacketHandler) {
// Send RPC SetPet, SetHat, SetSkin
sus.state.CurrentGame.CheckPetHatAndSkinNonce = net.GetNonce()
var f1a gamedata.RpcSub = &gamedata.RpcSetPet{PetID: 1}
var f2a packets.GameDataSubpacket = gamedata.NewRpcFromRpcSub(sus.state.CurrentGame.ClientPlayerNetID, &f1a)
var f1b gamedata.RpcSub = &gamedata.RpcSetHat{HatID: 1}
var f2b packets.GameDataSubpacket = gamedata.NewRpcFromRpcSub(sus.state.CurrentGame.ClientPlayerNetID, &f1b)
var f1c gamedata.RpcSub = &gamedata.RpcSetSkin{SkinID: 1}
var f2c packets.GameDataSubpacket = gamedata.NewRpcFromRpcSub(sus.state.CurrentGame.ClientPlayerNetID, &f1c)
var f3 protocol.HazelPayload = &packets.GameDataAll{GameID: sus.state.CurrentGame.GameID, Subpackets: []*protocol.Hazel{
f2a.AsHazel(),
f2b.AsHazel(),
f2c.AsHazel(),
}}
net.SendReliablePacket(sus.state.CurrentGame.CheckPetHatAndSkinNonce, []*protocol.Hazel{protocol.NewHazelFromPayload(&f3)})
}