Browse Source

A POC top down RPG/Nethack-like

Apollo 8 months ago
parent
commit
1b025949b8
9 changed files with 476 additions and 2 deletions
  1. 8 0
      .gitignore
  2. 1 1
      LICENSE
  3. 1 1
      README.md
  4. 69 0
      db.go
  5. 18 0
      go.mod
  6. 14 0
      go.sum
  7. 41 0
      main.go
  8. 64 0
      models.go
  9. 260 0
      vec2.go

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+doorgo/
+data/
+test_data/
+*.sys
+*.log
+*.db3
+cyber-realm
+

+ 1 - 1
LICENSE

@@ -1,5 +1,5 @@
 MIT License
-Copyright (c) <year> <copyright holders>
+Copyright (c) 2024 David Thielemann (Apollo@21:1/236)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 

+ 1 - 1
README.md

@@ -1,2 +1,2 @@
-# cyber-realm
+# Cyber Realm
 

+ 69 - 0
db.go

@@ -0,0 +1,69 @@
+package main
+
+import (
+	"fmt"
+
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+)
+
+type UserDB struct {
+	db *gorm.DB
+}
+
+func (udb *UserDB) Open(filename string) error {
+	var err error
+	udb.db, err = gorm.Open(sqlite.Open(filename), &gorm.Config{})
+	if err != nil {
+		return err
+	}
+	return udb.db.AutoMigrate(&User{})
+}
+
+func (udb *UserDB) FindUser(name string) (*User, error) {
+	var u *User
+	result := udb.db.First(&u, "name = ?", name)
+	if result.Error != nil {
+		return nil, result.Error
+	}
+	return u, nil
+}
+
+func (udb *UserDB) NameAvailable(name string) bool {
+	_, err := udb.FindUser(name)
+	return err != nil
+}
+
+func (udb *UserDB) SaveUser(u *User) error {
+	result := udb.db.Updates(u)
+	return result.Error
+}
+
+func (udb *UserDB) CreateUser(name, password string) error {
+	if !udb.NameAvailable(name) {
+		return fmt.Errorf("name taken")
+	}
+	var u *User = &User{
+		Name:      name,
+		Level:     1,
+		Loc:       NewVec2(),
+		WorldId:   1,
+		Gold:      200,
+		Health:    20,
+		MaxHealth: 20,
+	}
+	err := u.SetPassword(password)
+	if err != nil {
+		return err
+	}
+	result := udb.db.Create(&u)
+	return result.Error
+}
+
+func (udb *UserDB) Close() error {
+	sqldb, err := udb.db.DB()
+	if err != nil {
+		return err
+	}
+	return sqldb.Close()
+}

+ 18 - 0
go.mod

@@ -0,0 +1,18 @@
+module git.red-green.com/david/cyber-realm
+
+go 1.22.4
+
+replace git.red-green.com/RedGreen/doorgo => ./doorgo/door
+
+require (
+	golang.org/x/crypto v0.25.0
+	gorm.io/driver/sqlite v1.5.6
+	gorm.io/gorm v1.25.11
+)
+
+require (
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/mattn/go-sqlite3 v1.14.22 // indirect
+	golang.org/x/text v0.16.0 // indirect
+)

+ 14 - 0
go.sum

@@ -0,0 +1,14 @@
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
+golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
+gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
+gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
+gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

+ 41 - 0
main.go

@@ -0,0 +1,41 @@
+package main
+
+import "fmt"
+
+func main() {
+	// The UserDB would be run on the server
+	// But since most/100% of this is examples, we'll setup one up
+	db := &UserDB{}
+	err := db.Open("test_users.db3")
+	if err != nil {
+		fmt.Println("Err:", err)
+		return
+	}
+	defer db.Close()
+
+	// Example query
+	dummy, err := db.FindUser("Test Dummy")
+	if err != nil {
+		// Of course, we'd ask the user for their name and a password then use those
+		// But for our Test Dummy let's just make a dumb and simple setup
+		err = db.CreateUser("Test Dummy", "12345")
+		if err != nil {
+			fmt.Println("Err:", err)
+			return
+		}
+		dummy, err = db.FindUser("Test Dummy")
+		if err != nil {
+			fmt.Println("Err:", err)
+			return
+		}
+	}
+	//fmt.Printf("%#v\r\n", dummy)
+	// Example verification process
+	// I'd also want to verify they aren't trying to login in 2+ places at once
+	err = dummy.VerifyPassword("12345")
+	if err != nil {
+		fmt.Println("Wrong password?")
+	} else {
+		fmt.Printf("Yay, %s has logged in\r\n", dummy.Name)
+	}
+}

+ 64 - 0
models.go

@@ -0,0 +1,64 @@
+package main
+
+import (
+	"time"
+
+	"golang.org/x/crypto/bcrypt"
+)
+
+type Model struct {
+	Id        uint64 `gorm:"primaryKey"`
+	CreatedAt time.Time
+	UpdatedAt time.Time
+}
+
+type User struct {
+	Model
+	Name     string `gorm:"uniqueIndex,collate:no_case"`
+	Password string // bcrypted
+
+	Class uint8 // TODO: Convert this to an enum
+	Level uint32
+	Exp   uint32 // TODO: Decide if this needs 64-bit
+
+	Loc     *Vec2  `gorm:"embedded"` // Location within the world, Embedded so it's here in the User table
+	WorldId uint64 // TODO: Ensure this is in fact the World.Id
+
+	Gold     uint64 // On hand
+	GoldBank uint64 // In bank
+	RedGem   uint32 // TODO: Decide if this needs 64-bit
+	GreenGem uint32 // TODO: Decide if this needs 64-bit
+	BlueGem  uint32 // TODO: Decide if this needs 64-bit
+
+	Health    uint32 // Current Health, TODO: Decide if this needs 64-bit
+	MaxHealth uint32 // Maximum Health, TODO: Decide if this needs 64-bit
+	Magic     uint32 // Current Magic Points, TODO: Decide if this needs 64-bit
+	MaxMagic  uint32 // Maximum Magic Points, TODO: Decide if this needs 64-bit
+
+	Weapon uint8 // TODO: Convert this to an enum
+	Armor  uint8 // TODO: Convert this to an enum
+	Ring   uint8 // TODO: Convert this to an enum
+	Amulet uint8 // TODO: Convert this to an enum
+}
+
+func (u *User) SetPassword(password string) error {
+	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+	if err != nil {
+		return err
+	}
+	u.Password = string(hash)
+	return nil
+}
+
+func (u *User) VerifyPassword(password string) error {
+	err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
+	return err
+}
+
+func (u *User) PasswordCost() int {
+	cost, err := bcrypt.Cost([]byte(u.Password))
+	if err != nil {
+		return -1
+	}
+	return cost
+}

+ 260 - 0
vec2.go

@@ -0,0 +1,260 @@
+package main
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+type Vec2 struct {
+	X int64
+	Y int64
+}
+
+func NewVec2(axis ...int64) *Vec2 {
+	if len(axis) == 0 {
+		return &Vec2{}
+	} else if len(axis) == 1 {
+		return &Vec2{axis[0], axis[0]}
+	} else {
+		return &Vec2{axis[0], axis[1]}
+	}
+}
+
+func (v *Vec2) Clone() *Vec2 {
+	if v == nil {
+		return nil
+	}
+	return &Vec2{v.X, v.Y}
+}
+
+func (v *Vec2) Translate(axis ...int64) *Vec2 {
+	if v == nil {
+		return nil
+	}
+	if len(axis) == 0 {
+		return v
+	} else if len(axis) == 1 {
+		v.X += axis[0]
+		v.Y += axis[0]
+		return v
+	} else {
+		v.X += axis[0]
+		v.Y += axis[1]
+		return v
+	}
+}
+
+func (v *Vec2) TranslateBy(o *Vec2) *Vec2 {
+	if v == nil {
+		return nil
+	}
+	if o == nil {
+		return v
+	}
+	v.X += o.X
+	v.Y += o.Y
+	return v
+}
+
+func (v *Vec2) Assign(axis ...int64) *Vec2 {
+	if v == nil {
+		return nil
+	}
+	if len(axis) == 0 {
+		v.X = 0
+		v.Y = 0
+		return v
+	} else if len(axis) == 1 {
+		v.X = axis[0]
+		v.Y = axis[0]
+		return v
+	} else {
+		v.X = axis[0]
+		v.Y = axis[1]
+		return v
+	}
+}
+
+func (v *Vec2) AssignTo(o *Vec2) *Vec2 {
+	if v == nil {
+		return nil
+	}
+	if o == nil {
+		return v
+	}
+	v.X = o.X
+	v.Y = o.Y
+	return v
+}
+
+func (v *Vec2) Equal(axis ...int64) bool {
+	if v == nil {
+		return false
+	}
+	if len(axis) == 0 {
+		return v.X == 0 && v.Y == 0
+	} else if len(axis) == 1 {
+		return v.X == axis[0] && v.Y == axis[0]
+	} else {
+		return v.X == axis[0] && v.Y == axis[1]
+	}
+}
+
+func (v *Vec2) EqualTo(o *Vec2) bool {
+	if v == nil {
+		return false
+	}
+	if o == nil {
+		return v.X == 0 && v.Y == 0
+	}
+	return v.X == o.X && v.Y == o.Y
+}
+
+func (v *Vec2) Less(axis ...int64) bool {
+	if v == nil {
+		return false
+	}
+	if len(axis) == 0 {
+		return v.X <= 0 && v.Y <= 0
+	} else if len(axis) == 1 {
+		return v.X <= axis[0] && v.Y == axis[0]
+	} else {
+		return v.X <= axis[0] && v.Y == axis[1]
+	}
+}
+
+func (v *Vec2) LessThan(o *Vec2) bool {
+	if v == nil {
+		return false
+	}
+	if o == nil {
+		return v.Less()
+	}
+	return v.X <= o.X && v.Y <= o.Y
+}
+
+func (v *Vec2) Greater(axis ...int64) bool {
+	if v == nil {
+		return false
+	}
+	if len(axis) == 0 {
+		return v.X >= 0 && v.Y >= 0
+	} else if len(axis) == 1 {
+		return v.X >= axis[0] && v.Y >= axis[0]
+	} else {
+		return v.X >= axis[0] && v.Y >= axis[1]
+	}
+}
+
+func (v *Vec2) GreaterThan(o *Vec2) bool {
+	if v == nil {
+		return false
+	}
+	if o == nil {
+		return v.Greater()
+	}
+	return v.X >= o.X && v.Y >= o.Y
+}
+
+func SortVec2(v1, v2 *Vec2) (*Vec2, *Vec2) {
+	if v1.GreaterThan(v2) {
+		return v2, v1
+	}
+	return v1, v2
+}
+
+func (v *Vec2) Distance(axis ...int64) int64 {
+	if v == nil {
+		return 0
+	}
+	if len(axis) == 0 {
+		return v.X + v.Y
+	} else if len(axis) == 1 {
+		x := v.X - axis[0]
+		y := v.Y - axis[0]
+		if x < 0 {
+			x = -x
+		}
+		if y < 0 {
+			y = -y
+		}
+		return x + y
+	} else {
+		x := v.X - axis[0]
+		y := v.Y - axis[1]
+		if x < 0 {
+			x = -x
+		}
+		if y < 0 {
+			y = -y
+		}
+		return x + y
+	}
+}
+
+func (v *Vec2) DistanceTo(o *Vec2) int64 {
+	if v == nil {
+		return 0
+	}
+	if o == nil {
+		return v.Distance()
+	}
+	x := v.X - o.X
+	y := v.Y - o.Y
+	if x < 0 {
+		x = -x
+	}
+	if y < 0 {
+		y = -y
+	}
+	return x + y
+}
+
+func (v *Vec2) Abs() *Vec2 {
+	if v == nil {
+		return nil
+	}
+	if v.X < 0 {
+		v.X = -v.X
+	}
+	if v.Y < 0 {
+		v.Y = -v.Y
+	}
+	return v
+}
+
+func (v *Vec2) Negate() *Vec2 {
+	if v == nil {
+		return nil
+	}
+	v.X = -v.X
+	v.Y = -v.Y
+	return v
+}
+
+func (v *Vec2) String() string {
+	if v == nil {
+		return "nil"
+	}
+	return fmt.Sprintf("(%d, %d)", v.X, v.Y)
+}
+
+func Vec2FromString(s string) (*Vec2, string) {
+	if !strings.ContainsAny(s, "(),") {
+		return nil, "missing '(),' characters"
+	}
+	work := strings.Split(strings.ReplaceAll(strings.ReplaceAll(s, ")", ""), "(", ""), ", ")
+	if len(work) != 2 {
+		return nil, fmt.Sprintf("missing parts, got %d, need 2", len(work))
+	}
+	x, err := strconv.ParseInt(work[0], 10, 64)
+	if err != nil {
+		return nil, fmt.Sprintf("failed parsing x axis, %v", err)
+	}
+	y, err := strconv.ParseInt(work[1], 10, 64)
+	if err != nil {
+		return nil, fmt.Sprintf("failed parsing y axis, %v", err)
+	}
+	return NewVec2(x, y), ""
+}