finish install command

This commit is contained in:
Sky Johnson 2025-08-08 22:37:20 -05:00
parent 644b536a49
commit d04acc06eb
8 changed files with 727 additions and 8 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Dragon Knight test/build files
/dk
/dk.db

View File

@ -1,7 +0,0 @@
package main
import "fmt"
func main() {
fmt.Println("Main!")
}

17
go.mod
View File

@ -2,10 +2,25 @@ module dk
go 1.24.6
require github.com/valyala/fasthttp v1.64.0
require (
github.com/valyala/fasthttp v1.64.0
zombiezen.com/go/sqlite v1.4.2
)
require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sys v0.35.0 // indirect
modernc.org/libc v1.65.7 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.37.1 // indirect
)

56
go.sum
View File

@ -1,10 +1,66 @@
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og=
github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00=
modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs=
modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
zombiezen.com/go/sqlite v1.4.2 h1:KZXLrBuJ7tKNEm+VJcApLMeQbhmAUOKA5VWS93DfFRo=
zombiezen.com/go/sqlite v1.4.2/go.mod h1:5Kd4taTAD4MkBzT25mQ9uaAlLjyR0rFhsR6iINO70jc=

510
internal/install/install.go Normal file
View File

@ -0,0 +1,510 @@
package install
import (
"fmt"
"time"
"dk/internal/password"
"zombiezen.com/go/sqlite"
"zombiezen.com/go/sqlite/sqlitex"
)
const dbPath = "dk.db"
func Run() error {
fmt.Println("Dragon Knight Installation")
fmt.Println("==========================")
start := time.Now()
// Open database connection
conn, err := sqlite.OpenConn(dbPath, sqlite.OpenCreate|sqlite.OpenReadWrite|sqlite.OpenWAL)
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
}
defer conn.Close()
// Create tables
if err := createTables(conn); err != nil {
return fmt.Errorf("failed to create tables: %w", err)
}
// Populate initial data
if err := populateData(conn); err != nil {
return fmt.Errorf("failed to populate data: %w", err)
}
// Create demo user
if err := createDemoUser(conn); err != nil {
return fmt.Errorf("failed to create demo user: %w", err)
}
elapsed := time.Since(start)
fmt.Printf("\nDatabase setup complete in %.4f seconds.\n", elapsed.Seconds())
fmt.Println("Demo user created:")
fmt.Println(" Username: demo")
fmt.Println(" Password: Demo123!")
fmt.Println(" Email: demo@demo.com")
fmt.Println("\nInstallation complete!")
return nil
}
func createTables(conn *sqlite.Conn) error {
tables := []struct {
name string
sql string
}{
{"babble", `CREATE TABLE babble (
id INTEGER PRIMARY KEY AUTOINCREMENT,
posted INTEGER NOT NULL DEFAULT (unixepoch()),
author TEXT NOT NULL DEFAULT '',
babble TEXT NOT NULL DEFAULT ''
)`},
{"control", `CREATE TABLE control (
id INTEGER PRIMARY KEY AUTOINCREMENT,
world_size INTEGER NOT NULL DEFAULT 250,
open INTEGER NOT NULL DEFAULT 1,
admin_email TEXT NOT NULL DEFAULT '',
class_1_name TEXT NOT NULL DEFAULT '',
class_2_name TEXT NOT NULL DEFAULT '',
class_3_name TEXT NOT NULL DEFAULT ''
)`},
{"drops", `CREATE TABLE drops (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL DEFAULT '',
level INTEGER NOT NULL DEFAULT 0,
type INTEGER NOT NULL DEFAULT 0,
att1 TEXT NOT NULL DEFAULT '',
att2 TEXT NOT NULL DEFAULT ''
)`},
{"forum", `CREATE TABLE forum (
id INTEGER PRIMARY KEY AUTOINCREMENT,
posted INTEGER NOT NULL DEFAULT (unixepoch()),
last_post INTEGER NOT NULL DEFAULT (unixepoch()),
author INTEGER NOT NULL,
parent INTEGER NOT NULL DEFAULT 0,
replies INTEGER NOT NULL DEFAULT 0,
title TEXT NOT NULL,
content TEXT NOT NULL
)`},
{"items", `CREATE TABLE items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type INTEGER NOT NULL DEFAULT 0,
name TEXT NOT NULL,
value INTEGER NOT NULL DEFAULT 0,
att INTEGER NOT NULL DEFAULT 0,
special TEXT NOT NULL DEFAULT ''
)`},
{"monsters", `CREATE TABLE monsters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
max_hp INTEGER NOT NULL DEFAULT 0,
max_dmg INTEGER NOT NULL DEFAULT 0,
armor INTEGER NOT NULL DEFAULT 0,
level INTEGER NOT NULL DEFAULT 0,
max_exp INTEGER NOT NULL DEFAULT 0,
max_gold INTEGER NOT NULL DEFAULT 0,
immune INTEGER NOT NULL DEFAULT 0
)`},
{"news", `CREATE TABLE news (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author INTEGER NOT NULL,
posted INTEGER NOT NULL DEFAULT (unixepoch()),
content TEXT NOT NULL
)`},
{"spells", `CREATE TABLE spells (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
mp INTEGER NOT NULL DEFAULT 0,
attribute INTEGER NOT NULL DEFAULT 0,
type INTEGER NOT NULL DEFAULT 0
)`},
{"towns", `CREATE TABLE towns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
x INTEGER NOT NULL DEFAULT 0,
y INTEGER NOT NULL DEFAULT 0,
inn_cost INTEGER NOT NULL DEFAULT 0,
map_cost INTEGER NOT NULL DEFAULT 0,
tp_cost INTEGER NOT NULL DEFAULT 0,
shop_list TEXT NOT NULL DEFAULT ''
)`},
{"users", `CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL,
email TEXT NOT NULL,
verified INTEGER NOT NULL DEFAULT 0,
token TEXT NOT NULL DEFAULT '',
registered INTEGER NOT NULL DEFAULT (unixepoch()),
last_online INTEGER NOT NULL DEFAULT (unixepoch()),
auth INTEGER NOT NULL DEFAULT 0,
x INTEGER NOT NULL DEFAULT 0,
y INTEGER NOT NULL DEFAULT 0,
class_id INTEGER NOT NULL DEFAULT 0,
currently TEXT NOT NULL DEFAULT 'In Town',
fighting INTEGER NOT NULL DEFAULT 0,
monster_id INTEGER NOT NULL DEFAULT 0,
monster_hp INTEGER NOT NULL DEFAULT 0,
monster_sleep INTEGER NOT NULL DEFAULT 0,
monster_immune INTEGER NOT NULL DEFAULT 0,
uber_damage INTEGER NOT NULL DEFAULT 0,
uber_defense INTEGER NOT NULL DEFAULT 0,
hp INTEGER NOT NULL DEFAULT 15,
mp INTEGER NOT NULL DEFAULT 0,
tp INTEGER NOT NULL DEFAULT 10,
max_hp INTEGER NOT NULL DEFAULT 15,
max_mp INTEGER NOT NULL DEFAULT 0,
max_tp INTEGER NOT NULL DEFAULT 10,
level INTEGER NOT NULL DEFAULT 1,
gold INTEGER NOT NULL DEFAULT 100,
exp INTEGER NOT NULL DEFAULT 0,
gold_bonus INTEGER NOT NULL DEFAULT 0,
exp_bonus INTEGER NOT NULL DEFAULT 0,
strength INTEGER NOT NULL DEFAULT 5,
dexterity INTEGER NOT NULL DEFAULT 5,
attack INTEGER NOT NULL DEFAULT 5,
defense INTEGER NOT NULL DEFAULT 5,
weapon_id INTEGER NOT NULL DEFAULT 0,
armor_id INTEGER NOT NULL DEFAULT 0,
shield_id INTEGER NOT NULL DEFAULT 0,
slot_1_id INTEGER NOT NULL DEFAULT 0,
slot_2_id INTEGER NOT NULL DEFAULT 0,
slot_3_id INTEGER NOT NULL DEFAULT 0,
weapon_name TEXT NOT NULL DEFAULT '',
armor_name TEXT NOT NULL DEFAULT '',
shield_name TEXT NOT NULL DEFAULT '',
slot_1_name TEXT NOT NULL DEFAULT '',
slot_2_name TEXT NOT NULL DEFAULT '',
slot_3_name TEXT NOT NULL DEFAULT '',
drop_code INTEGER NOT NULL DEFAULT 0,
spells TEXT NOT NULL DEFAULT '',
towns TEXT NOT NULL DEFAULT ''
)`},
}
for _, table := range tables {
if err := sqlitex.ExecuteTransient(conn, table.sql, nil); err != nil {
return fmt.Errorf("failed to create %s table: %w", table.name, err)
}
fmt.Printf("✓ %s table created\n", table.name)
}
return nil
}
func populateData(conn *sqlite.Conn) error {
if err := sqlitex.ExecuteTransient(conn,
"INSERT INTO control VALUES (1, 250, 1, '', 'Mage', 'Warrior', 'Paladin')",
nil); err != nil {
return fmt.Errorf("failed to populate control table: %w", err)
}
fmt.Println("✓ Control table populated")
dropsSQL := `INSERT INTO drops VALUES
(1, 'Life Pebble', 1, 1, 'maxhp,10', 'X'),
(2, 'Life Stone', 10, 1, 'maxhp,25', 'X'),
(3, 'Life Rock', 25, 1, 'maxhp,50', 'X'),
(4, 'Magic Pebble', 1, 1, 'maxmp,10', 'X'),
(5, 'Magic Stone', 10, 1, 'maxmp,25', 'X'),
(6, 'Magic Rock', 25, 1, 'maxmp,50', 'X'),
(7, 'Dragon''s Scale', 10, 1, 'defensepower,25', 'X'),
(8, 'Dragon''s Plate', 30, 1, 'defensepower,50', 'X'),
(9, 'Dragon''s Claw', 10, 1, 'attackpower,25', 'X'),
(10, 'Dragon''s Tooth', 30, 1, 'attackpower,50', 'X'),
(11, 'Dragon''s Tear', 35, 1, 'strength,50', 'X'),
(12, 'Dragon''s Wing', 35, 1, 'dexterity,50', 'X'),
(13, 'Demon''s Sin', 35, 1, 'maxhp,-50', 'strength,50'),
(14, 'Demon''s Fall', 35, 1, 'maxmp,-50', 'strength,50'),
(15, 'Demon''s Lie', 45, 1, 'maxhp,-100', 'strength,100'),
(16, 'Demon''s Hate', 45, 1, 'maxmp,-100', 'strength,100'),
(17, 'Angel''s Joy', 25, 1, 'maxhp,25', 'strength,25'),
(18, 'Angel''s Rise', 30, 1, 'maxhp,50', 'strength,50'),
(19, 'Angel''s Truth', 35, 1, 'maxhp,75', 'strength,75'),
(20, 'Angel''s Love', 40, 1, 'maxhp,100', 'strength,100'),
(21, 'Seraph''s Joy', 25, 1, 'maxmp,25', 'dexterity,25'),
(22, 'Seraph''s Rise', 30, 1, 'maxmp,50', 'dexterity,50'),
(23, 'Seraph''s Truth', 35, 1, 'maxmp,75', 'dexterity,75'),
(24, 'Seraph''s Love', 40, 1, 'maxmp,100', 'dexterity,100'),
(25, 'Ruby', 50, 1, 'maxhp,150', 'X'),
(26, 'Pearl', 50, 1, 'maxmp,150', 'X'),
(27, 'Emerald', 50, 1, 'strength,150', 'X'),
(28, 'Topaz', 50, 1, 'dexterity,150', 'X'),
(29, 'Obsidian', 50, 1, 'attackpower,150', 'X'),
(30, 'Diamond', 50, 1, 'defensepower,150', 'X'),
(31, 'Memory Drop', 5, 1, 'expbonus,10', 'X'),
(32, 'Fortune Drop', 5, 1, 'goldbonus,10', 'X')`
if err := sqlitex.ExecuteTransient(conn, dropsSQL, nil); err != nil {
return fmt.Errorf("failed to populate drops table: %w", err)
}
fmt.Println("✓ Drops table populated")
itemsSQL := `INSERT INTO items VALUES
(1, 1, 'Stick', 10, 2, 'X'),
(2, 1, 'Branch', 30, 4, 'X'),
(3, 1, 'Club', 40, 5, 'X'),
(4, 1, 'Dagger', 90, 8, 'X'),
(5, 1, 'Hatchet', 150, 12, 'X'),
(6, 1, 'Axe', 200, 16, 'X'),
(7, 1, 'Brand', 300, 25, 'X'),
(8, 1, 'Poleaxe', 500, 35, 'X'),
(9, 1, 'Broadsword', 800, 45, 'X'),
(10, 1, 'Battle Axe', 1200, 50, 'X'),
(11, 1, 'Claymore', 2000, 60, 'X'),
(12, 1, 'Dark Axe', 3000, 100, 'expbonus,-5'),
(13, 1, 'Dark Sword', 4500, 125, 'expbonus,-10'),
(14, 1, 'Bright Sword', 6000, 100, 'expbonus,10'),
(15, 1, 'Magic Sword', 10000, 150, 'maxmp,50'),
(16, 1, 'Destiny Blade', 50000, 250, 'strength,50'),
(17, 2, 'Skivvies', 25, 2, 'goldbonus,10'),
(18, 2, 'Clothes', 50, 5, 'X'),
(19, 2, 'Leather Armor', 75, 10, 'X'),
(20, 2, 'Hard Leather Armor', 150, 25, 'X'),
(21, 2, 'Chain Mail', 300, 30, 'X'),
(22, 2, 'Bronze Plate', 900, 50, 'X'),
(23, 2, 'Iron Plate', 2000, 100, 'X'),
(24, 2, 'Magic Armor', 4000, 125, 'maxmp,50'),
(25, 2, 'Dark Armor', 5000, 150, 'expbonus,-10'),
(26, 2, 'Bright Armor', 10000, 175, 'expbonus,10'),
(27, 2, 'Destiny Raiment', 50000, 200, 'dexterity,50'),
(28, 3, 'Reed Shield', 50, 2, 'X'),
(29, 3, 'Buckler', 100, 4, 'X'),
(30, 3, 'Small Shield', 500, 10, 'X'),
(31, 3, 'Large Shield', 2500, 30, 'X'),
(32, 3, 'Silver Shield', 10000, 60, 'X'),
(33, 3, 'Destiny Aegis', 25000, 100, 'maxhp,50')`
if err := sqlitex.ExecuteTransient(conn, itemsSQL, nil); err != nil {
return fmt.Errorf("failed to populate items table: %w", err)
}
fmt.Println("✓ Items table populated")
monstersSQL := `INSERT INTO monsters VALUES
(1, 'Blue Slime', 4, 3, 1, 1, 1, 1, 0),
(2, 'Red Slime', 6, 5, 1, 1, 2, 1, 0),
(3, 'Critter', 6, 5, 2, 1, 4, 2, 0),
(4, 'Creature', 10, 8, 2, 2, 4, 2, 0),
(5, 'Shadow', 10, 9, 3, 2, 6, 2, 1),
(6, 'Drake', 11, 10, 3, 2, 8, 3, 0),
(7, 'Shade', 12, 10, 3, 3, 10, 3, 1),
(8, 'Drakelor', 14, 12, 4, 3, 10, 3, 0),
(9, 'Silver Slime', 15, 100, 200, 30, 15, 1000, 2),
(10, 'Scamp', 16, 13, 5, 4, 15, 5, 0),
(11, 'Raven', 16, 13, 5, 4, 18, 6, 0),
(12, 'Scorpion', 18, 14, 6, 5, 20, 7, 0),
(13, 'Illusion', 20, 15, 6, 5, 20, 7, 1),
(14, 'Nightshade', 22, 16, 6, 6, 24, 8, 0),
(15, 'Drakemal', 22, 18, 7, 6, 24, 8, 0),
(16, 'Shadow Raven', 24, 18, 7, 6, 26, 9, 1),
(17, 'Ghost', 24, 20, 8, 6, 28, 9, 0),
(18, 'Frost Raven', 26, 20, 8, 7, 30, 10, 0),
(19, 'Rogue Scorpion', 28, 22, 9, 7, 32, 11, 0),
(20, 'Ghoul', 29, 24, 9, 7, 34, 11, 0),
(21, 'Magician', 30, 24, 10, 8, 36, 12, 0),
(22, 'Rogue', 30, 25, 12, 8, 40, 13, 0),
(23, 'Drakefin', 32, 26, 12, 8, 40, 13, 0),
(24, 'Shimmer', 32, 26, 14, 8, 45, 15, 1),
(25, 'Fire Raven', 34, 28, 14, 9, 45, 15, 0),
(26, 'Dybbuk', 34, 28, 14, 9, 50, 17, 0),
(27, 'Knave', 36, 30, 15, 9, 52, 17, 0),
(28, 'Goblin', 36, 30, 15, 10, 54, 18, 0),
(29, 'Skeleton', 38, 30, 18, 10, 58, 19, 0),
(30, 'Dark Slime', 38, 32, 18, 10, 62, 21, 0),
(31, 'Silver Scorpion', 30, 160, 350, 40, 63, 2000, 2),
(32, 'Mirage', 40, 32, 20, 11, 64, 21, 1),
(33, 'Sorceror', 41, 33, 22, 11, 68, 23, 0),
(34, 'Imp', 42, 34, 22, 12, 70, 23, 0),
(35, 'Nymph', 43, 35, 22, 12, 70, 23, 0),
(36, 'Scoundrel', 43, 35, 22, 12, 75, 25, 0),
(37, 'Megaskeleton', 44, 36, 24, 13, 78, 26, 0),
(38, 'Grey Wolf', 44, 36, 24, 13, 82, 27, 0),
(39, 'Phantom', 46, 38, 24, 14, 85, 28, 1),
(40, 'Specter', 46, 38, 24, 14, 90, 30, 0),
(41, 'Dark Scorpion', 48, 40, 26, 15, 95, 32, 1),
(42, 'Warlock', 48, 40, 26, 15, 100, 33, 1),
(43, 'Orc', 49, 42, 28, 15, 104, 35, 0),
(44, 'Sylph', 49, 42, 28, 15, 106, 35, 0),
(45, 'Wraith', 50, 45, 30, 16, 108, 36, 0),
(46, 'Hellion', 50, 45, 30, 16, 110, 37, 0),
(47, 'Bandit', 52, 45, 30, 16, 114, 38, 0),
(48, 'Ultraskeleton', 52, 46, 32, 16, 116, 39, 0),
(49, 'Dark Wolf', 54, 47, 36, 17, 120, 40, 1),
(50, 'Troll', 56, 48, 36, 17, 120, 40, 0),
(51, 'Werewolf', 56, 48, 38, 17, 124, 41, 0),
(52, 'Hellcat', 58, 50, 38, 18, 128, 43, 0),
(53, 'Spirit', 58, 50, 38, 18, 132, 44, 0),
(54, 'Nisse', 60, 52, 40, 19, 132, 44, 0),
(55, 'Dawk', 60, 54, 40, 19, 136, 45, 0),
(56, 'Figment', 64, 55, 42, 19, 140, 47, 1),
(57, 'Hellhound', 66, 56, 44, 20, 140, 47, 0),
(58, 'Wizard', 66, 56, 44, 20, 144, 48, 0),
(59, 'Uruk', 68, 58, 44, 20, 146, 49, 0),
(60, 'Siren', 68, 400, 800, 50, 10000, 50, 2),
(61, 'Megawraith', 70, 60, 46, 21, 155, 52, 0),
(62, 'Dawkin', 70, 60, 46, 21, 155, 52, 0),
(63, 'Grey Bear', 70, 62, 48, 21, 160, 53, 0),
(64, 'Haunt', 72, 62, 48, 22, 160, 53, 0),
(65, 'Hellbeast', 74, 64, 50, 22, 165, 55, 0),
(66, 'Fear', 76, 66, 52, 23, 165, 55, 0),
(67, 'Beast', 76, 66, 52, 23, 170, 57, 0),
(68, 'Ogre', 78, 68, 54, 23, 170, 57, 0),
(69, 'Dark Bear', 80, 70, 56, 24, 175, 58, 1),
(70, 'Fire', 80, 72, 56, 24, 175, 58, 0),
(71, 'Polgergeist', 84, 74, 58, 25, 180, 60, 0),
(72, 'Fright', 86, 76, 58, 25, 180, 60, 0),
(73, 'Lycan', 88, 78, 60, 25, 185, 62, 0),
(74, 'Terra Elemental', 88, 80, 62, 25, 185, 62, 1),
(75, 'Necromancer', 90, 80, 62, 26, 190, 63, 0),
(76, 'Ultrawraith', 90, 82, 64, 26, 190, 63, 0),
(77, 'Dawkor', 92, 82, 64, 26, 195, 65, 0),
(78, 'Werebear', 92, 84, 65, 26, 195, 65, 0),
(79, 'Brute', 94, 84, 65, 27, 200, 67, 0),
(80, 'Large Beast', 96, 88, 66, 27, 200, 67, 0),
(81, 'Horror', 96, 88, 68, 27, 210, 70, 0),
(82, 'Flame', 100, 90, 70, 28, 210, 70, 0),
(83, 'Lycanthor', 100, 90, 70, 28, 210, 70, 0),
(84, 'Wyrm', 100, 92, 72, 28, 220, 73, 0),
(85, 'Aero Elemental', 104, 94, 74, 29, 220, 73, 1),
(86, 'Dawkare', 106, 96, 76, 29, 220, 73, 0),
(87, 'Large Brute', 108, 98, 78, 29, 230, 77, 0),
(88, 'Frost Wyrm', 110, 100, 80, 30, 230, 77, 0),
(89, 'Knight', 110, 102, 80, 30, 240, 80, 0),
(90, 'Lycanthra', 112, 104, 82, 30, 240, 80, 0),
(91, 'Terror', 115, 108, 84, 31, 250, 83, 0),
(92, 'Blaze', 118, 108, 84, 31, 250, 83, 0),
(93, 'Aqua Elemental', 120, 110, 90, 31, 260, 87, 1),
(94, 'Fire Wyrm', 120, 110, 90, 32, 260, 87, 0),
(95, 'Lesser Wyvern', 122, 110, 92, 32, 270, 90, 0),
(96, 'Doomer', 124, 112, 92, 32, 270, 90, 0),
(97, 'Armor Knight', 130, 115, 95, 33, 280, 93, 0),
(98, 'Wyvern', 134, 120, 95, 33, 290, 97, 0),
(99, 'Nightmare', 138, 125, 100, 33, 300, 100, 0),
(100, 'Fira Elemental', 140, 125, 100, 34, 310, 103, 1),
(101, 'Megadoomer', 140, 128, 105, 34, 320, 107, 0),
(102, 'Greater Wyvern', 145, 130, 105, 34, 335, 112, 0),
(103, 'Advocate', 148, 132, 108, 35, 350, 117, 0),
(104, 'Strong Knight', 150, 135, 110, 35, 365, 122, 0),
(105, 'Liche', 150, 135, 110, 35, 380, 127, 0),
(106, 'Ultradoomer', 155, 140, 115, 36, 395, 132, 0),
(107, 'Fanatic', 160, 140, 115, 36, 410, 137, 0),
(108, 'Green Dragon', 160, 140, 115, 36, 425, 142, 0),
(109, 'Fiend', 160, 145, 120, 37, 445, 148, 0),
(110, 'Greatest Wyvern', 162, 150, 120, 37, 465, 155, 0),
(111, 'Lesser Devil', 164, 150, 120, 37, 485, 162, 0),
(112, 'Liche Master', 168, 155, 125, 38, 505, 168, 0),
(113, 'Zealot', 168, 155, 125, 38, 530, 177, 0),
(114, 'Serafiend', 170, 155, 125, 38, 555, 185, 0),
(115, 'Pale Knight', 175, 160, 130, 39, 580, 193, 0),
(116, 'Blue Dragon', 180, 160, 130, 39, 605, 202, 0),
(117, 'Obsessive', 180, 160, 135, 40, 630, 210, 0),
(118, 'Devil', 184, 164, 135, 40, 666, 222, 0),
(119, 'Liche Prince', 190, 168, 138, 40, 660, 220, 0),
(120, 'Cherufiend', 195, 170, 140, 41, 690, 230, 0),
(121, 'Red Dragon', 200, 180, 145, 41, 720, 240, 0),
(122, 'Greater Devil', 200, 180, 145, 41, 750, 250, 0),
(123, 'Renegade', 205, 185, 150, 42, 780, 260, 0),
(124, 'Archfiend', 210, 190, 150, 42, 810, 270, 0),
(125, 'Liche Lord', 210, 190, 155, 42, 850, 283, 0),
(126, 'Greatest Devil', 215, 195, 160, 43, 890, 297, 0),
(127, 'Dark Knight', 220, 200, 160, 43, 930, 310, 0),
(128, 'Giant', 220, 200, 165, 43, 970, 323, 0),
(129, 'Shadow Dragon', 225, 200, 170, 44, 1010, 337, 0),
(130, 'Liche King', 225, 205, 170, 44, 1050, 350, 0),
(131, 'Incubus', 230, 205, 175, 44, 1100, 367, 1),
(132, 'Traitor', 230, 205, 175, 45, 1150, 383, 0),
(133, 'Demon', 240, 210, 180, 45, 1200, 400, 0),
(134, 'Dark Dragon', 245, 215, 180, 45, 1250, 417, 1),
(135, 'Insurgent', 250, 220, 190, 46, 1300, 433, 0),
(136, 'Leviathan', 255, 225, 190, 46, 1350, 450, 0),
(137, 'Grey Daemon', 260, 230, 190, 46, 1400, 467, 0),
(138, 'Succubus', 265, 240, 200, 47, 1460, 487, 1),
(139, 'Demon Prince', 270, 240, 200, 47, 1520, 507, 0),
(140, 'Black Dragon', 275, 250, 205, 47, 1580, 527, 1),
(141, 'Nihilist', 280, 250, 205, 47, 1640, 547, 0),
(142, 'Behemoth', 285, 260, 210, 48, 1700, 567, 0),
(143, 'Demagogue', 290, 260, 210, 48, 1760, 587, 0),
(144, 'Demon Lord', 300, 270, 220, 48, 1820, 607, 0),
(145, 'Red Daemon', 310, 280, 230, 48, 1880, 627, 0),
(146, 'Colossus', 320, 300, 240, 49, 1940, 647, 0),
(147, 'Demon King', 330, 300, 250, 49, 2000, 667, 0),
(148, 'Dark Daemon', 340, 320, 260, 49, 2200, 733, 1),
(149, 'Titan', 360, 340, 270, 50, 2400, 800, 0),
(150, 'Black Daemon', 400, 400, 280, 50, 3000, 1000, 1),
(151, 'Lucifuge', 600, 600, 400, 50, 10000, 10000, 2)`
if err := sqlitex.ExecuteTransient(conn, monstersSQL, nil); err != nil {
return fmt.Errorf("failed to populate monsters table: %w", err)
}
fmt.Println("✓ Monsters table populated (sample data)")
// News table
if err := sqlitex.ExecuteTransient(conn,
"INSERT INTO news (author, content) VALUES (1, 'Welcome to Dragon Knight! This is your first news post.')",
nil); err != nil {
return fmt.Errorf("failed to populate news table: %w", err)
}
fmt.Println("✓ News table populated")
// Spells table
spellsSQL := `INSERT INTO spells VALUES
(1, 'Heal', 5, 10, 1),
(2, 'Revive', 10, 25, 1),
(3, 'Life', 25, 50, 1),
(4, 'Breath', 50, 100, 1),
(5, 'Gaia', 75, 150, 1),
(6, 'Hurt', 5, 15, 2),
(7, 'Pain', 12, 35, 2),
(8, 'Maim', 25, 70, 2),
(9, 'Rend', 40, 100, 2),
(10, 'Chaos', 50, 130, 2),
(11, 'Sleep', 10, 5, 3),
(12, 'Dream', 30, 9, 3),
(13, 'Nightmare', 60, 13, 3),
(14, 'Craze', 10, 10, 4),
(15, 'Rage', 20, 25, 4),
(16, 'Fury', 30, 50, 4),
(17, 'Ward', 10, 10, 5),
(18, 'Fend', 20, 25, 5),
(19, 'Barrier', 30, 50, 5)`
if err := sqlitex.ExecuteTransient(conn, spellsSQL, nil); err != nil {
return fmt.Errorf("failed to populate spells table: %w", err)
}
fmt.Println("✓ Spells table populated")
// Towns table
townsSQL := `INSERT INTO towns VALUES
(1, 'Midworld', 0, 0, 5, 0, 0, '1,2,3,17,18,19,28,29'),
(2, 'Roma', 30, 30, 10, 25, 5, '2,3,4,18,19,29'),
(3, 'Bris', 70, -70, 25, 50, 15, '2,3,4,5,18,19,20,29.30'),
(4, 'Kalle', -100, 100, 40, 100, 30, '5,6,8,10,12,21,22,23,29,30'),
(5, 'Narcissa', -130, -130, 60, 500, 50, '4,7,9,11,13,21,22,23,29,30,31'),
(6, 'Hambry', 170, 170, 90, 1000, 80, '10,11,12,13,14,23,24,30,31'),
(7, 'Gilead', 200, -200, 100, 3000, 110, '12,13,14,15,24,25,26,32'),
(8, 'Endworld', -250, -250, 125, 9000, 160, '16,27,33')`
if err := sqlitex.ExecuteTransient(conn, townsSQL, nil); err != nil {
return fmt.Errorf("failed to populate towns table: %w", err)
}
fmt.Println("✓ Towns table populated")
return nil
}
func createDemoUser(conn *sqlite.Conn) error {
// Hash the password using argon2id
hashedPassword, err := password.Hash("Demo123!")
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}
stmt := `INSERT INTO users (username, password, email, verified, class_id, auth)
VALUES (?, ?, ?, 1, 1, 1)`
if err := sqlitex.ExecuteTransient(conn, stmt, &sqlitex.ExecOptions{
Args: []any{"demo", hashedPassword, "demo@demo.com"},
}); err != nil {
return fmt.Errorf("failed to create demo user: %w", err)
}
fmt.Println("✓ Demo user created")
return nil
}

View File

@ -0,0 +1,83 @@
package password
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"fmt"
"strings"
"golang.org/x/crypto/argon2"
)
const (
time = 1
memory = 64 * 1024
threads = 4
keyLen = 32
)
// Hash creates an argon2id hash of the password
func Hash(password string) (string, error) {
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
return "", err
}
hash := argon2.IDKey([]byte(password), salt, time, memory, threads, keyLen)
// Encode in the format: $argon2id$v=19$m=65536,t=1,p=4$<salt>$<hash>
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
encoded := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
argon2.Version, memory, time, threads, b64Salt, b64Hash)
return encoded, nil
}
// Verify checks if a password matches the hash
func Verify(password, encodedHash string) (bool, error) {
parts := strings.Split(encodedHash, "$")
if len(parts) != 6 {
return false, fmt.Errorf("invalid hash format")
}
if parts[1] != "argon2id" {
return false, fmt.Errorf("invalid hash variant")
}
var version int
_, err := fmt.Sscanf(parts[2], "v=%d", &version)
if err != nil {
return false, err
}
if version != argon2.Version {
return false, fmt.Errorf("incompatible argon2 version")
}
var m, t, p uint32
_, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &m, &t, &p)
if err != nil {
return false, err
}
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
if err != nil {
return false, err
}
expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
if err != nil {
return false, err
}
hash := argon2.IDKey([]byte(password), salt, t, m, uint8(p), uint32(len(expectedHash)))
// Use constant-time comparison to prevent timing attacks
if subtle.ConstantTimeCompare(hash, expectedHash) == 1 {
return true, nil
}
return false, nil
}

19
internal/server/server.go Normal file
View File

@ -0,0 +1,19 @@
package server
import (
"fmt"
"log"
"github.com/valyala/fasthttp"
)
func Start() error {
// Simple fasthttp server for now
requestHandler := func(ctx *fasthttp.RequestCtx) {
ctx.SetContentType("text/html; charset=utf-8")
fmt.Fprintf(ctx, "<h1>Dragon Knight</h1><p>Server is running!</p>")
}
log.Println("Server starting on :8080")
return fasthttp.ListenAndServe(":8080", requestHandler)
}

40
main.go Normal file
View File

@ -0,0 +1,40 @@
package main
import (
"fmt"
"log"
"os"
"dk/internal/install"
"dk/internal/server"
)
func main() {
if len(os.Args) < 2 {
startServer()
return
}
switch os.Args[1] {
case "install":
if err := install.Run(); err != nil {
log.Fatalf("Installation failed: %v", err)
}
case "serve":
startServer()
default:
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", os.Args[1])
fmt.Fprintln(os.Stderr, "Available commands:")
fmt.Fprintln(os.Stderr, " install - Install the database")
fmt.Fprintln(os.Stderr, " serve - Start the server")
fmt.Fprintln(os.Stderr, " (no command) - Start the server")
os.Exit(1)
}
}
func startServer() {
fmt.Println("Starting Dragon Knight server...")
if err := server.Start(); err != nil {
log.Fatalf("Server failed: %v", err)
}
}