296 lines
9.2 KiB
Go
296 lines
9.2 KiB
Go
|
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)})
|
||
|
}
|