elbot/main.go

517 lines
13 KiB
Go

package main
import (
"bufio"
"encoding/binary"
"fmt"
"log"
"net"
"net/http"
"os"
"strconv"
"strings"
"text/template"
"time"
"github.com/spf13/viper"
)
var items []item
var inventar []invPos
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
var gActors = make(map[int]string)
var isInTrade int
var myTrade [16]tradeStr
var hisTrade [16]tradeStr
// read items list and create the array
func populateItems() {
file, err := os.Open("item_info.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
a := strings.Split(string(scanner.Text()), "|")
id, _ := strconv.Atoi(strings.ReplaceAll(a[0], " ", ""))
text_id := strings.TrimSpace(a[1])
item_price, _ := strconv.ParseFloat(strings.TrimSpace(a[2]), 32)
n := item{Id: id, Text: text_id, Price: item_price}
items = append(items, n)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
// Process incoming text messages from game
func processPmMessage(c net.Conn, user string, mesaj string) {
// fmt.Printf("PM MESSAGE command: |%s|\n", mesaj)
msg := strings.Split(mesaj, " ")[1]
switch msg {
case "#ver", "ver":
sendMessage(c, user, "GoEl bot v1 - written by mihaim")
case "#inv", "inv":
sendMessage(c, user, "Bot inventory:")
for i := 0; i < 36; i++ {
if inventar[i].Quantity != 0 {
for _, it := range items {
if inventar[i].Id == it.Id {
sendMessage(c, user, "Item: "+strconv.Itoa(inventar[i].Quantity)+" "+it.Text+" sell price: "+fmt.Sprintf("%.2f", it.Price))
}
}
}
}
case "#set":
if len(strings.Split(mesaj, " ")) != 4 {
sendMessage(c, user, "#set <item_id> <price>")
break
}
itemId, _ := strconv.ParseInt(strings.Split(mesaj, " ")[2], 0, 0)
newPrice, _ := strconv.ParseFloat(strings.Split(mesaj, " ")[3], 64)
items[itemId].Price = newPrice
a1 := fmt.Sprintf("Set price for %s to %.2f", items[itemId].Text, items[itemId].Price)
sendMessage(c, user, a1)
case "#get", "get":
if len(strings.Split(mesaj, " ")) != 3 {
sendMessage(c, user, "#get <item_id>")
break
}
itemId, _ := strconv.ParseInt(strings.Split(mesaj, " ")[2], 0, 0)
a1 := fmt.Sprintf("Price for %s is %.2f", items[itemId].Text, items[itemId].Price)
sendMessage(c, user, a1)
case "#buy", "buy":
if len(strings.Split(mesaj, " ")) < 3 {
sendMessage(c, user, "usage: #buy <quantity> <item>")
break
}
if isInTrade == 0 {
sendMessage(c, user, "You need to trade with me before buying something")
break
}
q, _ := strconv.ParseInt(strings.Split(mesaj, " ")[2], 0, 0)
search_for := strings.Join(strings.Split(mesaj, " ")[3:], " ")
// search in inventory , add to trade window
i := 0
for i = 0; i < 36; i++ {
if inventar[i].Quantity != 0 && strings.Contains(strings.ToLower(items[inventar[i].Id].Text), strings.ToLower(search_for)) {
putItemOnTrade(c, int(q), i)
sendMessage(c, user, fmt.Sprintf("Added to trade window %d %s", int(q), items[inventar[i].Id].Text))
break
}
}
case "#help", "help":
sendMessage(c, user, "GoEl trade bot v1 - written by mihaim")
sendMessage(c, user, "Available commands: #buy, #inv, #ver, #help")
case "#acc", "acc":
acceptTrade(c)
//sendMessage(c, user, "Accepting trade")
case "#dbg", "dbg":
for i := 0; i < 16; i++ {
if hisTrade[i].Quantity != 0 {
sendMessage(c, user, fmt.Sprintf("His_Trade: %s %d\n", items[hisTrade[i].Id].Text, hisTrade[i].Quantity))
}
}
for i := 0; i < 16; i++ {
if myTrade[i].Quantity != 0 {
sendMessage(c, user, fmt.Sprintf("My_Trade: %s %d\n", items[myTrade[i].Id].Text, myTrade[i].Quantity))
}
}
default:
fmt.Printf("command: |%s|", strings.Split(mesaj, " ")[1])
switch user {
case "mihaim":
sendMessage(c, user, "Sar'na conashu' mihai")
case "radu":
sendMessage(c, user, "Sa traiasca dom' shef. Dulapu raporteaza ca nu's probleme")
case "domino":
sendMessage(c, user, "Traiasca dom' sherif. Dulapu la raport . Niem problem. All good")
default:
sendMessage(c, user, "Unknown command but hello to you too . And use #help for more informations")
}
}
}
func processBuffer(c net.Conn, buffer []byte, length int) {
ping_reply := make([]byte, 7)
switch buffer[0] {
// received inventory
case HERE_YOUR_INVENTORY:
for i := 0; i < int(buffer[3]); i++ {
quantity := binary.LittleEndian.Uint32(buffer[i*10+3+3 : i*10+3+3+4])
itemId := int(buffer[i*10+3+3+3+3]) + 256*int(buffer[i*10+3+3+3+4])
pos := int(buffer[i*10+3+7])
inventar[pos].Id = itemId
inventar[pos].Quantity = int(quantity)
inventar[pos].Pos = pos
}
// Ping from server
case PING_REQUEST:
a := int(buffer[1]) + 256*int(buffer[2])
ping_reply[0] = buffer[0]
ping_reply[1] = buffer[1]
ping_reply[2] = buffer[2]
ping_reply[3] = buffer[3]
ping_reply[4] = buffer[4]
ping_reply[5] = buffer[5]
ping_reply[6] = buffer[6]
fmt.Printf("->Command: %d %d PING_REQUEST\n", buffer[0], a)
binary.Write(c, binary.LittleEndian, ping_reply)
// text message received
case RAW_TEXT:
// trade request
if strings.Split(string(buffer[5:]), " ")[1] == "wants" {
name := strings.Split(string(buffer[5:]), " ")[0]
//sendMessage(c, name, "Wanna trade? "+fmt.Sprint(is_in_trade))
// if we have the actor ... send trade
// Oare ce dracu se intimpla daca nu exista actorul ... :D will see
for key, value := range gActors {
// sendMessage(c, name, "Wanna trade? |"+value+"|")
if strings.Split(value, " ")[0] == name {
sendTrade(c, key)
isInTrade = 1
break
}
}
}
// normal PM message
if string(buffer[5:14]) == "[PM from " {
msg := buffer[14 : length-1]
dela := strings.Split(string(msg), ":")
pm := string(strings.Join(dela[1:], ":"))
processPmMessage(c, dela[0], pm)
break
}
if strings.Split(string(buffer[5:]), " ")[1] == "aborted" {
isInTrade = 0
resetTrade()
}
fmt.Printf("Alt mesaj |%s|\n", buffer[5:])
// add new actor
case ADD_NEW_ACTOR:
actorName := make([]byte, 30)
actorId := int(buffer[3]) + 256*int(buffer[4])
for i := 0; i < 30; i++ {
if buffer[i+20] != 0 {
actorName[i] = buffer[i+20]
} else {
break
}
}
act := strings.TrimRight(string(actorName), "\t \n\x00")
gActors[actorId] = act
fmt.Printf("NE Actor: Id: %d Name: |%s|\n", actorId, act)
// new minute
case NEW_MINUTE:
break
case GET_TRADE_OBJECT:
fmt.Printf("GET_TRADE_OBJECT ")
img := int(buffer[3]) + 256*int(buffer[4])
qua := int(buffer[5]) + 256*int(buffer[6]) + 65535*(int(buffer[7])+256*int(buffer[8]))
pos := int(buffer[7+3])
tipe := int(buffer[3+6])
us := int(buffer[11])
id := int(buffer[12]) + 256*int(buffer[13])
if us == 0 {
fmt.Printf("Noi am pus %d %s \n", qua, items[id].Text)
myTrade[pos].Id = id
myTrade[pos].ImageId = img
myTrade[pos].Quantity += qua
myTrade[pos].Type = tipe
} else {
fmt.Printf("El a pus %d %s \n", qua, items[id].Text)
hisTrade[pos].Id = id
hisTrade[pos].ImageId = img
hisTrade[pos].Quantity += qua
hisTrade[pos].Type = tipe
}
for i := 0; i < length-3; i++ {
fmt.Printf("%2x ", buffer[i+3])
}
fmt.Printf("\n")
case REMOVE_TRADE_OBJECT:
fmt.Printf("REMOVE_TRADE_OBJECT ")
qua := int(buffer[3]) + 256*int(buffer[4]) + 65535*(int(buffer[5])+256*int(buffer[6]))
pos := int(buffer[3+4])
us := int(buffer[3+5])
if us == 0 {
fmt.Printf("Noi am retras %d %s \n", qua, items[myTrade[pos].Id].Text)
myTrade[pos].Quantity -= qua
} else {
fmt.Printf("El a retras %d %s \n", qua, items[hisTrade[pos].Id].Text)
hisTrade[pos].Quantity -= qua
}
for i := 0; i < length-3; i++ {
fmt.Printf("%2x ", buffer[i+3])
}
fmt.Printf("\n")
case GET_TRADE_ACCEPT:
// fmt.Printf("GET_TRADE ACCEPT %02x %02x %02x %02x\n", buffer[0], buffer[1], buffer[2], buffer[3])
if buffer[3] == 0 {
break
}
trade_value := 0.0
for i := 0; i < 16; i++ {
trade_value += float64(myTrade[i].Quantity) * float64(items[myTrade[i].Id].Price)
}
fmt.Printf("Total trade value %d\n", int(trade_value))
for i := 0; i < 16; i++ {
//fmt.Printf("GET_TRADE_ACCEPT: index %d valoare = %d %d %d \n", i, his_trade[i].Id, his_trade[i].Quantity, int(trade_value))
if hisTrade[i].Id == MONEY && int(hisTrade[i].Quantity) == int(trade_value) {
//fmt.Printf("GET_TRADE_ACCEPT: valoare = %d %d \n", his_trade[i].Quantity, int(trade_value))
acceptTrade(c)
break
}
}
//fmt.Printf("GET_TRADE_ACCEPT: valoare = %d \n", int(trade_value))
case GET_TRADE_REJECT:
fmt.Printf("GET_TRADE REJECT\n")
case GET_TRADE_EXIT:
fmt.Printf("GET_TRADE EXIT\n")
// get partial stat
case SEND_PARTIAL_STAT:
// fmt.Printf("get_partial_stat from server")
break
// add new enhanced actor
case ADD_NEW_ENHANCED_ACTOR:
actorName := make([]byte, 30)
actorId := int(buffer[3]) + 256*int(buffer[4])
for i := 0; i < 30; i++ {
if buffer[i+31] != 0 {
actorName[i] = buffer[i+31]
} else {
break
}
}
act := strings.TrimRight(string(actorName), "\t \n\x00")
gActors[actorId] = act
fmt.Printf("Actor: Id: %d Name: |%s|\n", actorId, act)
// remove actor
case REMOVE_ACTOR:
actorId := int(buffer[3]) + 256*int(buffer[4])
delete(gActors, actorId)
fmt.Printf("Remove Actor Id: %d \n", actorId)
// add actor command ( do nothing with this )
case ADD_ACTOR_COMMAND:
break
// everything else ( commands not implemented yet)
default:
a := int(buffer[1]) + 256*int(buffer[2])
fmt.Printf("->Command: %d %d\n", buffer[0], a)
fmt.Printf("->: %s \n", buffer[3:])
}
}
func main() {
isInTrade = 0
resetTrade()
viper.SetConfigName("config")
viper.AddConfigPath(".")
viper.AutomaticEnv()
viper.SetConfigType("yml")
var configuration configurations
populateItems()
// Start adding web interface and handlers ( for now listen on 8080 . move it to config.yml in the end)
http.HandleFunc("/hello", webHello)
http.HandleFunc("/", webSlash)
http.HandleFunc("/inv", webShowInventory)
http.HandleFunc("/save", saveItemsAndPrices)
http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/test.tmpl"))
data := webPageData{
PageTitle: "Items list and prices",
Ite: items,
}
tmpl.Execute(w, data)
})
go func() {
fmt.Println("listen on 8082")
log.Fatal(http.ListenAndServe(":8082", nil))
}()
if err := viper.ReadInConfig(); err != nil {
fmt.Printf("Error reading config file, %s", err)
}
if err := viper.Unmarshal(&configuration); err != nil {
fmt.Printf("Unable to decode into struct, %s", err)
}
c, err := net.Dial("tcp", configuration.Server.Ip+":"+fmt.Sprint(configuration.Server.Port))
if err != nil {
fmt.Println(err)
return
}
// initial inventory is empty
for i := 0; i < 36; i++ {
var v1 = new(invPos)
v1.Id = 0
v1.Quantity = 0
v1.Pos = 0
inventar = append(inventar, *v1)
}
binary.Write(c, binary.LittleEndian, sendLogin(configuration.Credential.User, configuration.Credential.Password))
sec := time.Now().Unix()
// main loop for communicating with el server
// One thread to rule them all :P ( and keep us connected)
go func() {
for {
if time.Now().Unix()-sec > 30 {
sendHeartBeat(c)
sec = time.Now().Unix()
fmt.Printf(time.Now().Format("2006-01-02 15:04:05") + "Avoid lagging out. Sending heartbeat pack\n")
}
time.Sleep(2 * time.Second)
}
}()
for {
reply := make([]byte, 4096)
length, err := c.Read(reply)
checkErr(err)
//fmt.Printf("Received command: %d and length %d \n", reply[0], length)
//fmt.Printf("Hex dump: %s\n", hex.Dump(reply))
indexul := 0
for ok := true; ok; ok = (indexul+int(reply[indexul+1])+256*int(reply[indexul+2]) < length) {
packLen := int(reply[indexul+1]) + 256*int(reply[indexul+2])
if packLen >= 2 {
process := reply[indexul : indexul+packLen+2]
// fmt.Printf("->Command imbinata: %d %s\n", process[0], process[3:])
processBuffer(c, process, len(process))
}
indexul = indexul + int(reply[indexul+1]) + 256*int(reply[indexul+2]) + 2
}
}
}
func webHello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello\n")
// testing modify variable from web
isInTrade++
}
func webSlash(w http.ResponseWriter, req *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/home.tmpl"))
data := webPageData{
PageTitle: "Home page",
Ite: nil,
}
tmpl.Execute(w, data)
}
func webShowInventory(w http.ResponseWriter, req *http.Request) {
var inv_full []item
// Create structure to be rendered in webpage
for i := 0; i < 36; i++ {
if inventar[i].Quantity != 0 {
for _, it := range items {
if inventar[i].Id == it.Id {
n := item{Id: it.Id, Text: it.Text, Price: it.Price, Quantity: inventar[i].Quantity}
inv_full = append(inv_full, n)
}
}
}
}
tmpl := template.Must(template.ParseFiles("templates/inv.tmpl"))
tmpl.Execute(w, inv_full)
}
func saveItemsAndPrices(w http.ResponseWriter, req *http.Request) {
// a test
// items[0].Price++
file, err := os.OpenFile("item_info.txt", os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
datawriter := bufio.NewWriter(file)
for _, data := range items {
_, _ = datawriter.WriteString(fmt.Sprintf("%d | %s | %.2f\n", data.Id, data.Text, data.Price))
}
datawriter.Flush()
}
func resetTrade() {
fmt.Printf("Reset Trade\n")
for i := 0; i < 16; i++ {
myTrade[i].Id = 0
myTrade[i].ImageId = 0
myTrade[i].Quantity = 0
myTrade[i].Type = 0
hisTrade[i].Id = 0
hisTrade[i].ImageId = 0
hisTrade[i].Quantity = 0
hisTrade[i].Type = 0
}
}