work on converting to in-memory
This commit is contained in:
parent
71245b1655
commit
958a7098a2
226
data/drops.json
Normal file
226
data/drops.json
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Life Pebble",
|
||||||
|
"level": 1,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxhp,10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Life Stone",
|
||||||
|
"level": 10,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxhp,25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Life Rock",
|
||||||
|
"level": 25,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxhp,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Magic Pebble",
|
||||||
|
"level": 1,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxmp,10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Magic Stone",
|
||||||
|
"level": 10,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxmp,25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "Magic Rock",
|
||||||
|
"level": 25,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxmp,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "Dragon's Scale",
|
||||||
|
"level": 10,
|
||||||
|
"type": 1,
|
||||||
|
"att": "defensepower,25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "Dragon's Plate",
|
||||||
|
"level": 30,
|
||||||
|
"type": 1,
|
||||||
|
"att": "defensepower,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "Dragon's Claw",
|
||||||
|
"level": 10,
|
||||||
|
"type": 1,
|
||||||
|
"att": "attackpower,25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "Dragon's Tooth",
|
||||||
|
"level": 30,
|
||||||
|
"type": 1,
|
||||||
|
"att": "attackpower,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"name": "Dragon's Tear",
|
||||||
|
"level": 35,
|
||||||
|
"type": 1,
|
||||||
|
"att": "strength,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"name": "Dragon's Wing",
|
||||||
|
"level": 35,
|
||||||
|
"type": 1,
|
||||||
|
"att": "dexterity,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"name": "Demon's Sin",
|
||||||
|
"level": 35,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxhp,-50,strength,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"name": "Demon's Fall",
|
||||||
|
"level": 35,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxmp,-50,strength,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"name": "Demon's Lie",
|
||||||
|
"level": 45,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxhp,-100,strength,100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"name": "Demon's Hate",
|
||||||
|
"level": 45,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxmp,-100,strength,100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"name": "Angel's Joy",
|
||||||
|
"level": 25,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxhp,25,strength,25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"name": "Angel's Rise",
|
||||||
|
"level": 30,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxhp,50,strength,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"name": "Angel's Truth",
|
||||||
|
"level": 35,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxhp,75,strength,75"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"name": "Angel's Love",
|
||||||
|
"level": 40,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxhp,100,strength,100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"name": "Seraph's Joy",
|
||||||
|
"level": 25,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxmp,25,dexterity,25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"name": "Seraph's Rise",
|
||||||
|
"level": 30,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxmp,50,dexterity,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"name": "Seraph's Truth",
|
||||||
|
"level": 35,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxmp,75,dexterity,75"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"name": "Seraph's Love",
|
||||||
|
"level": 40,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxmp,100,dexterity,100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"name": "Ruby",
|
||||||
|
"level": 50,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxhp,150"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"name": "Pearl",
|
||||||
|
"level": 50,
|
||||||
|
"type": 1,
|
||||||
|
"att": "maxmp,150"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"name": "Emerald",
|
||||||
|
"level": 50,
|
||||||
|
"type": 1,
|
||||||
|
"att": "strength,150"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"name": "Topaz",
|
||||||
|
"level": 50,
|
||||||
|
"type": 1,
|
||||||
|
"att": "dexterity,150"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"name": "Obsidian",
|
||||||
|
"level": 50,
|
||||||
|
"type": 1,
|
||||||
|
"att": "attackpower,150"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 30,
|
||||||
|
"name": "Diamond",
|
||||||
|
"level": 50,
|
||||||
|
"type": 1,
|
||||||
|
"att": "defensepower,150"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"name": "Memory Drop",
|
||||||
|
"level": 5,
|
||||||
|
"type": 1,
|
||||||
|
"att": "expbonus,10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"name": "Fortune Drop",
|
||||||
|
"level": 5,
|
||||||
|
"type": 1,
|
||||||
|
"att": "goldbonus,10"
|
||||||
|
}
|
||||||
|
]
|
266
data/items.json
Normal file
266
data/items.json
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Stick",
|
||||||
|
"value": 10,
|
||||||
|
"att": 2,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Branch",
|
||||||
|
"value": 30,
|
||||||
|
"att": 4,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Club",
|
||||||
|
"value": 40,
|
||||||
|
"att": 5,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Dagger",
|
||||||
|
"value": 90,
|
||||||
|
"att": 8,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Hatchet",
|
||||||
|
"value": 150,
|
||||||
|
"att": 12,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Axe",
|
||||||
|
"value": 200,
|
||||||
|
"att": 16,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Brand",
|
||||||
|
"value": 300,
|
||||||
|
"att": 25,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Poleaxe",
|
||||||
|
"value": 500,
|
||||||
|
"att": 35,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Broadsword",
|
||||||
|
"value": 800,
|
||||||
|
"att": 45,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Battle Axe",
|
||||||
|
"value": 1200,
|
||||||
|
"att": 50,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Claymore",
|
||||||
|
"value": 2000,
|
||||||
|
"att": 60,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Dark Axe",
|
||||||
|
"value": 3000,
|
||||||
|
"att": 100,
|
||||||
|
"special": "expbonus,-5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Dark Sword",
|
||||||
|
"value": 4500,
|
||||||
|
"att": 125,
|
||||||
|
"special": "expbonus,-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Bright Sword",
|
||||||
|
"value": 6000,
|
||||||
|
"att": 100,
|
||||||
|
"special": "expbonus,10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Magic Sword",
|
||||||
|
"value": 10000,
|
||||||
|
"att": 150,
|
||||||
|
"special": "maxmp,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Destiny Blade",
|
||||||
|
"value": 50000,
|
||||||
|
"att": 250,
|
||||||
|
"special": "strength,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Skivvies",
|
||||||
|
"value": 25,
|
||||||
|
"att": 2,
|
||||||
|
"special": "goldbonus,10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Clothes",
|
||||||
|
"value": 50,
|
||||||
|
"att": 5,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Leather Armor",
|
||||||
|
"value": 75,
|
||||||
|
"att": 10,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Hard Leather Armor",
|
||||||
|
"value": 150,
|
||||||
|
"att": 25,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Chain Mail",
|
||||||
|
"value": 300,
|
||||||
|
"att": 30,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Bronze Plate",
|
||||||
|
"value": 900,
|
||||||
|
"att": 50,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Iron Plate",
|
||||||
|
"value": 2000,
|
||||||
|
"att": 100,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Magic Armor",
|
||||||
|
"value": 4000,
|
||||||
|
"att": 125,
|
||||||
|
"special": "maxmp,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Dark Armor",
|
||||||
|
"value": 5000,
|
||||||
|
"att": 150,
|
||||||
|
"special": "expbonus,-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Bright Armor",
|
||||||
|
"value": 10000,
|
||||||
|
"att": 175,
|
||||||
|
"special": "expbonus,10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"type": 2,
|
||||||
|
"name": "Destiny Raiment",
|
||||||
|
"value": 50000,
|
||||||
|
"att": 200,
|
||||||
|
"special": "dexterity,50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"type": 3,
|
||||||
|
"name": "Reed Shield",
|
||||||
|
"value": 50,
|
||||||
|
"att": 2,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"type": 3,
|
||||||
|
"name": "Buckler",
|
||||||
|
"value": 100,
|
||||||
|
"att": 4,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 30,
|
||||||
|
"type": 3,
|
||||||
|
"name": "Small Shield",
|
||||||
|
"value": 500,
|
||||||
|
"att": 10,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"type": 3,
|
||||||
|
"name": "Large Shield",
|
||||||
|
"value": 2500,
|
||||||
|
"att": 30,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"type": 3,
|
||||||
|
"name": "Silver Shield",
|
||||||
|
"value": 10000,
|
||||||
|
"att": 60,
|
||||||
|
"special": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"type": 3,
|
||||||
|
"name": "Destiny Aegis",
|
||||||
|
"value": 25000,
|
||||||
|
"att": 100,
|
||||||
|
"special": "maxhp,50"
|
||||||
|
}
|
||||||
|
]
|
1663
data/monsters.json
Normal file
1663
data/monsters.json
Normal file
File diff suppressed because it is too large
Load Diff
135
data/spells.json
Normal file
135
data/spells.json
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Heal",
|
||||||
|
"mp": 5,
|
||||||
|
"attribute": 10,
|
||||||
|
"type": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Revive",
|
||||||
|
"mp": 10,
|
||||||
|
"attribute": 25,
|
||||||
|
"type": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Life",
|
||||||
|
"mp": 25,
|
||||||
|
"attribute": 50,
|
||||||
|
"type": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Breath",
|
||||||
|
"mp": 50,
|
||||||
|
"attribute": 100,
|
||||||
|
"type": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Gaia",
|
||||||
|
"mp": 75,
|
||||||
|
"attribute": 150,
|
||||||
|
"type": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "Hurt",
|
||||||
|
"mp": 5,
|
||||||
|
"attribute": 15,
|
||||||
|
"type": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "Pain",
|
||||||
|
"mp": 12,
|
||||||
|
"attribute": 35,
|
||||||
|
"type": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "Maim",
|
||||||
|
"mp": 25,
|
||||||
|
"attribute": 70,
|
||||||
|
"type": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "Rend",
|
||||||
|
"mp": 40,
|
||||||
|
"attribute": 100,
|
||||||
|
"type": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "Chaos",
|
||||||
|
"mp": 50,
|
||||||
|
"attribute": 130,
|
||||||
|
"type": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"name": "Sleep",
|
||||||
|
"mp": 10,
|
||||||
|
"attribute": 5,
|
||||||
|
"type": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"name": "Dream",
|
||||||
|
"mp": 30,
|
||||||
|
"attribute": 9,
|
||||||
|
"type": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"name": "Nightmare",
|
||||||
|
"mp": 60,
|
||||||
|
"attribute": 13,
|
||||||
|
"type": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"name": "Craze",
|
||||||
|
"mp": 10,
|
||||||
|
"attribute": 10,
|
||||||
|
"type": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"name": "Rage",
|
||||||
|
"mp": 20,
|
||||||
|
"attribute": 25,
|
||||||
|
"type": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"name": "Fury",
|
||||||
|
"mp": 30,
|
||||||
|
"attribute": 50,
|
||||||
|
"type": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"name": "Ward",
|
||||||
|
"mp": 10,
|
||||||
|
"attribute": 10,
|
||||||
|
"type": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"name": "Fend",
|
||||||
|
"mp": 20,
|
||||||
|
"attribute": 25,
|
||||||
|
"type": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"name": "Barrier",
|
||||||
|
"mp": 30,
|
||||||
|
"attribute": 50,
|
||||||
|
"type": 5
|
||||||
|
}
|
||||||
|
]
|
82
data/towns.json
Normal file
82
data/towns.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Midworld",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"inn_cost": 5,
|
||||||
|
"map_cost": 0,
|
||||||
|
"tp_cost": 0,
|
||||||
|
"shop_list": "1,2,3,17,18,19,28,29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Roma",
|
||||||
|
"x": 30,
|
||||||
|
"y": 30,
|
||||||
|
"inn_cost": 10,
|
||||||
|
"map_cost": 25,
|
||||||
|
"tp_cost": 5,
|
||||||
|
"shop_list": "2,3,4,18,19,29"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Bris",
|
||||||
|
"x": 70,
|
||||||
|
"y": -70,
|
||||||
|
"inn_cost": 25,
|
||||||
|
"map_cost": 50,
|
||||||
|
"tp_cost": 15,
|
||||||
|
"shop_list": "2,3,4,5,18,19,20,29.30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Kalle",
|
||||||
|
"x": -100,
|
||||||
|
"y": 100,
|
||||||
|
"inn_cost": 40,
|
||||||
|
"map_cost": 100,
|
||||||
|
"tp_cost": 30,
|
||||||
|
"shop_list": "5,6,8,10,12,21,22,23,29,30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Narcissa",
|
||||||
|
"x": -130,
|
||||||
|
"y": -130,
|
||||||
|
"inn_cost": 60,
|
||||||
|
"map_cost": 500,
|
||||||
|
"tp_cost": 50,
|
||||||
|
"shop_list": "4,7,9,11,13,21,22,23,29,30,31"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "Hambry",
|
||||||
|
"x": 170,
|
||||||
|
"y": 170,
|
||||||
|
"inn_cost": 90,
|
||||||
|
"map_cost": 1000,
|
||||||
|
"tp_cost": 80,
|
||||||
|
"shop_list": "10,11,12,13,14,23,24,30,31"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "Gilead",
|
||||||
|
"x": 200,
|
||||||
|
"y": -200,
|
||||||
|
"inn_cost": 100,
|
||||||
|
"map_cost": 3000,
|
||||||
|
"tp_cost": 110,
|
||||||
|
"shop_list": "12,13,14,15,24,25,26,32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "Endworld",
|
||||||
|
"x": -250,
|
||||||
|
"y": -250,
|
||||||
|
"inn_cost": 125,
|
||||||
|
"map_cost": 9000,
|
||||||
|
"tp_cost": 160,
|
||||||
|
"shop_list": "16,27,33"
|
||||||
|
}
|
||||||
|
]
|
@ -1,500 +0,0 @@
|
|||||||
// package install is the home of the install command
|
|
||||||
//
|
|
||||||
// Its purpose is to set up the intial database structure and data,
|
|
||||||
// then create a "demo" user to act as the initial admin account.
|
|
||||||
//
|
|
||||||
// At the moment, it simply creates a static structure and admin user;
|
|
||||||
// in the future I'd like to add migrations and prompt for account
|
|
||||||
// creation.
|
|
||||||
package install
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"dk/internal/database"
|
|
||||||
"dk/internal/password"
|
|
||||||
"dk/internal/users"
|
|
||||||
)
|
|
||||||
|
|
||||||
const dbPath = "dk.db"
|
|
||||||
|
|
||||||
func Run() error {
|
|
||||||
fmt.Println("Dragon Knight Installation")
|
|
||||||
fmt.Println("==========================")
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
if err := database.Init("dk.db"); err != nil {
|
|
||||||
return fmt.Errorf("failed to initialize database: %w", err)
|
|
||||||
}
|
|
||||||
defer database.Close()
|
|
||||||
|
|
||||||
if err := createTables(); err != nil {
|
|
||||||
return fmt.Errorf("failed to create tables: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := populateData(); err != nil {
|
|
||||||
return fmt.Errorf("failed to populate data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := createDemoUser(); 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() 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,
|
|
||||||
att 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 := database.Exec(table.sql); 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() error {
|
|
||||||
if err := database.Exec("INSERT INTO control VALUES (1, 250, 1, '', 'Mage', 'Warrior', 'Paladin')"); 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'),
|
|
||||||
(2, 'Life Stone', 10, 1, 'maxhp,25'),
|
|
||||||
(3, 'Life Rock', 25, 1, 'maxhp,50'),
|
|
||||||
(4, 'Magic Pebble', 1, 1, 'maxmp,10'),
|
|
||||||
(5, 'Magic Stone', 10, 1, 'maxmp,25'),
|
|
||||||
(6, 'Magic Rock', 25, 1, 'maxmp,50'),
|
|
||||||
(7, 'Dragon''s Scale', 10, 1, 'defensepower,25'),
|
|
||||||
(8, 'Dragon''s Plate', 30, 1, 'defensepower,50'),
|
|
||||||
(9, 'Dragon''s Claw', 10, 1, 'attackpower,25'),
|
|
||||||
(10, 'Dragon''s Tooth', 30, 1, 'attackpower,50'),
|
|
||||||
(11, 'Dragon''s Tear', 35, 1, 'strength,50'),
|
|
||||||
(12, 'Dragon''s Wing', 35, 1, 'dexterity,50'),
|
|
||||||
(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'),
|
|
||||||
(26, 'Pearl', 50, 1, 'maxmp,150'),
|
|
||||||
(27, 'Emerald', 50, 1, 'strength,150'),
|
|
||||||
(28, 'Topaz', 50, 1, 'dexterity,150'),
|
|
||||||
(29, 'Obsidian', 50, 1, 'attackpower,150'),
|
|
||||||
(30, 'Diamond', 50, 1, 'defensepower,150'),
|
|
||||||
(31, 'Memory Drop', 5, 1, 'expbonus,10'),
|
|
||||||
(32, 'Fortune Drop', 5, 1, 'goldbonus,10')`
|
|
||||||
if err := database.Exec(dropsSQL); 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, ''),
|
|
||||||
(2, 1, 'Branch', 30, 4, ''),
|
|
||||||
(3, 1, 'Club', 40, 5, ''),
|
|
||||||
(4, 1, 'Dagger', 90, 8, ''),
|
|
||||||
(5, 1, 'Hatchet', 150, 12, ''),
|
|
||||||
(6, 1, 'Axe', 200, 16, ''),
|
|
||||||
(7, 1, 'Brand', 300, 25, ''),
|
|
||||||
(8, 1, 'Poleaxe', 500, 35, ''),
|
|
||||||
(9, 1, 'Broadsword', 800, 45, ''),
|
|
||||||
(10, 1, 'Battle Axe', 1200, 50, ''),
|
|
||||||
(11, 1, 'Claymore', 2000, 60, ''),
|
|
||||||
(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, ''),
|
|
||||||
(19, 2, 'Leather Armor', 75, 10, ''),
|
|
||||||
(20, 2, 'Hard Leather Armor', 150, 25, ''),
|
|
||||||
(21, 2, 'Chain Mail', 300, 30, ''),
|
|
||||||
(22, 2, 'Bronze Plate', 900, 50, ''),
|
|
||||||
(23, 2, 'Iron Plate', 2000, 100, ''),
|
|
||||||
(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, ''),
|
|
||||||
(29, 3, 'Buckler', 100, 4, ''),
|
|
||||||
(30, 3, 'Small Shield', 500, 10, ''),
|
|
||||||
(31, 3, 'Large Shield', 2500, 30, ''),
|
|
||||||
(32, 3, 'Silver Shield', 10000, 60, ''),
|
|
||||||
(33, 3, 'Destiny Aegis', 25000, 100, 'maxhp,50')`
|
|
||||||
if err := database.Exec(itemsSQL); 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 := database.Exec(monstersSQL); err != nil {
|
|
||||||
return fmt.Errorf("failed to populate monsters table: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ monsters table populated")
|
|
||||||
|
|
||||||
if err := database.Exec("INSERT INTO news (author, content) VALUES (1, 'Welcome to Dragon Knight! This is your first news post.')"); err != nil {
|
|
||||||
return fmt.Errorf("failed to populate news table: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ news table populated")
|
|
||||||
|
|
||||||
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 := database.Exec(spellsSQL); err != nil {
|
|
||||||
return fmt.Errorf("failed to populate spells table: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ spells table populated")
|
|
||||||
|
|
||||||
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 := database.Exec(townsSQL); err != nil {
|
|
||||||
return fmt.Errorf("failed to populate towns table: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("✓ towns table populated")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDemoUser() error {
|
|
||||||
user := users.New()
|
|
||||||
user.Username = "demo"
|
|
||||||
user.Email = "demo@demo.com"
|
|
||||||
user.Password = password.Hash("Demo123!")
|
|
||||||
user.ClassID = 1
|
|
||||||
user.Auth = 4
|
|
||||||
|
|
||||||
if err := user.Insert(); err != nil {
|
|
||||||
return fmt.Errorf("failed to create demo user: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("✓ Demo user created")
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,51 +1,37 @@
|
|||||||
package monsters
|
package monsters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"dk/internal/database"
|
"path/filepath"
|
||||||
"dk/internal/helpers/scanner"
|
"sort"
|
||||||
|
"sync"
|
||||||
"zombiezen.com/go/sqlite"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Monster represents a monster in the database
|
// Monster represents a monster in the game
|
||||||
type Monster struct {
|
type Monster struct {
|
||||||
database.BaseModel
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
ID int `db:"id" json:"id"`
|
MaxHP int `json:"max_hp"`
|
||||||
Name string `db:"name" json:"name"`
|
MaxDmg int `json:"max_dmg"`
|
||||||
MaxHP int `db:"max_hp" json:"max_hp"`
|
Armor int `json:"armor"`
|
||||||
MaxDmg int `db:"max_dmg" json:"max_dmg"`
|
Level int `json:"level"`
|
||||||
Armor int `db:"armor" json:"armor"`
|
MaxExp int `json:"max_exp"`
|
||||||
Level int `db:"level" json:"level"`
|
MaxGold int `json:"max_gold"`
|
||||||
MaxExp int `db:"max_exp" json:"max_exp"`
|
Immune int `json:"immune"`
|
||||||
MaxGold int `db:"max_gold" json:"max_gold"`
|
|
||||||
Immune int `db:"immune" json:"immune"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monster) GetTableName() string {
|
|
||||||
return "monsters"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monster) GetID() int {
|
|
||||||
return m.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monster) SetID(id int) {
|
|
||||||
m.ID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Monster) Set(field string, value any) error {
|
|
||||||
return database.Set(m, field, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monster) Save() error {
|
func (m *Monster) Save() error {
|
||||||
return database.Save(m)
|
store := GetStore()
|
||||||
|
store.UpdateMonster(m)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monster) Delete() error {
|
func (m *Monster) Delete() error {
|
||||||
return database.Delete(m)
|
store := GetStore()
|
||||||
|
store.RemoveMonster(m.ID)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new Monster with sensible defaults
|
// Creates a new Monster with sensible defaults
|
||||||
@ -62,19 +48,6 @@ func New() *Monster {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var monsterScanner = scanner.New[Monster]()
|
|
||||||
|
|
||||||
// Returns the column list for monster queries
|
|
||||||
func monsterColumns() string {
|
|
||||||
return monsterScanner.Columns()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populates a Monster struct using the fast scanner
|
|
||||||
func scanMonster(stmt *sqlite.Stmt) *Monster {
|
|
||||||
monster := &Monster{}
|
|
||||||
monsterScanner.Scan(stmt, monster)
|
|
||||||
return monster
|
|
||||||
}
|
|
||||||
|
|
||||||
// Immunity constants for monster immunity types
|
// Immunity constants for monster immunity types
|
||||||
const (
|
const (
|
||||||
@ -83,109 +56,423 @@ const (
|
|||||||
ImmuneSleep = 2 // Immune to Sleep spells
|
ImmuneSleep = 2 // Immune to Sleep spells
|
||||||
)
|
)
|
||||||
|
|
||||||
// Retrieves a monster by ID
|
// MonsterStore provides in-memory storage with O(1) lookups
|
||||||
func Find(id int) (*Monster, error) {
|
type MonsterStore struct {
|
||||||
var monster *Monster
|
monsters map[int]*Monster // ID -> Monster (O(1))
|
||||||
|
byLevel map[int][]*Monster // Level -> []*Monster (O(1) to get slice)
|
||||||
query := `SELECT ` + monsterColumns() + ` FROM monsters WHERE id = ?`
|
byImmunity map[int][]*Monster // Immunity -> []*Monster (O(1) to get slice)
|
||||||
|
allByLevel []*Monster // Pre-sorted by level, id
|
||||||
err := database.Query(query, func(stmt *sqlite.Stmt) error {
|
maxID int
|
||||||
monster = scanMonster(stmt)
|
mu sync.RWMutex
|
||||||
return nil
|
|
||||||
}, id)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to find monster: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if monster == nil {
|
// Global in-memory store
|
||||||
|
var store *MonsterStore
|
||||||
|
var storeOnce sync.Once
|
||||||
|
|
||||||
|
// Initialize the in-memory store
|
||||||
|
func initStore() {
|
||||||
|
store = &MonsterStore{
|
||||||
|
monsters: make(map[int]*Monster),
|
||||||
|
byLevel: make(map[int][]*Monster),
|
||||||
|
byImmunity: make(map[int][]*Monster),
|
||||||
|
allByLevel: make([]*Monster, 0),
|
||||||
|
maxID: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStore returns the global monster store
|
||||||
|
func GetStore() *MonsterStore {
|
||||||
|
storeOnce.Do(initStore)
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMonster adds a monster to the in-memory store and updates all indices
|
||||||
|
func (ms *MonsterStore) AddMonster(monster *Monster) {
|
||||||
|
ms.mu.Lock()
|
||||||
|
defer ms.mu.Unlock()
|
||||||
|
|
||||||
|
// Add to primary store
|
||||||
|
ms.monsters[monster.ID] = monster
|
||||||
|
|
||||||
|
// Update max ID
|
||||||
|
if monster.ID > ms.maxID {
|
||||||
|
ms.maxID = monster.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to level index
|
||||||
|
ms.byLevel[monster.Level] = append(ms.byLevel[monster.Level], monster)
|
||||||
|
|
||||||
|
// Add to immunity index
|
||||||
|
ms.byImmunity[monster.Immune] = append(ms.byImmunity[monster.Immune], monster)
|
||||||
|
|
||||||
|
// Add to sorted list and re-sort
|
||||||
|
ms.allByLevel = append(ms.allByLevel, monster)
|
||||||
|
sort.Slice(ms.allByLevel, func(i, j int) bool {
|
||||||
|
if ms.allByLevel[i].Level == ms.allByLevel[j].Level {
|
||||||
|
return ms.allByLevel[i].ID < ms.allByLevel[j].ID
|
||||||
|
}
|
||||||
|
return ms.allByLevel[i].Level < ms.allByLevel[j].Level
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sort level index
|
||||||
|
sort.Slice(ms.byLevel[monster.Level], func(i, j int) bool {
|
||||||
|
return ms.byLevel[monster.Level][i].ID < ms.byLevel[monster.Level][j].ID
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sort immunity index
|
||||||
|
sort.Slice(ms.byImmunity[monster.Immune], func(i, j int) bool {
|
||||||
|
if ms.byImmunity[monster.Immune][i].Level == ms.byImmunity[monster.Immune][j].Level {
|
||||||
|
return ms.byImmunity[monster.Immune][i].ID < ms.byImmunity[monster.Immune][j].ID
|
||||||
|
}
|
||||||
|
return ms.byImmunity[monster.Immune][i].Level < ms.byImmunity[monster.Immune][j].Level
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveMonster removes a monster from the store and updates indices
|
||||||
|
func (ms *MonsterStore) RemoveMonster(id int) {
|
||||||
|
ms.mu.Lock()
|
||||||
|
defer ms.mu.Unlock()
|
||||||
|
|
||||||
|
monster, exists := ms.monsters[id]
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from primary store
|
||||||
|
delete(ms.monsters, id)
|
||||||
|
|
||||||
|
// Remove from level index
|
||||||
|
levelMonsters := ms.byLevel[monster.Level]
|
||||||
|
for i, m := range levelMonsters {
|
||||||
|
if m.ID == id {
|
||||||
|
ms.byLevel[monster.Level] = append(levelMonsters[:i], levelMonsters[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from immunity index
|
||||||
|
immunityMonsters := ms.byImmunity[monster.Immune]
|
||||||
|
for i, m := range immunityMonsters {
|
||||||
|
if m.ID == id {
|
||||||
|
ms.byImmunity[monster.Immune] = append(immunityMonsters[:i], immunityMonsters[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from sorted list
|
||||||
|
for i, m := range ms.allByLevel {
|
||||||
|
if m.ID == id {
|
||||||
|
ms.allByLevel = append(ms.allByLevel[:i], ms.allByLevel[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMonster updates a monster and rebuilds indices
|
||||||
|
func (ms *MonsterStore) UpdateMonster(monster *Monster) {
|
||||||
|
ms.RemoveMonster(monster.ID)
|
||||||
|
ms.AddMonster(monster)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextID returns the next available ID
|
||||||
|
func (ms *MonsterStore) GetNextID() int {
|
||||||
|
ms.mu.RLock()
|
||||||
|
defer ms.mu.RUnlock()
|
||||||
|
return ms.maxID + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFromJSON loads monster data from a JSON file
|
||||||
|
func (ms *MonsterStore) LoadFromJSON(filename string) error {
|
||||||
|
ms.mu.Lock()
|
||||||
|
defer ms.mu.Unlock()
|
||||||
|
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil // File doesn't exist, start with empty store
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to read monsters JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle empty file
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil // Empty file, start with empty store
|
||||||
|
}
|
||||||
|
|
||||||
|
var monsters []*Monster
|
||||||
|
if err := json.Unmarshal(data, &monsters); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal monsters JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing data
|
||||||
|
ms.monsters = make(map[int]*Monster)
|
||||||
|
ms.byLevel = make(map[int][]*Monster)
|
||||||
|
ms.byImmunity = make(map[int][]*Monster)
|
||||||
|
ms.allByLevel = make([]*Monster, 0)
|
||||||
|
ms.maxID = 0
|
||||||
|
|
||||||
|
// Add all monsters
|
||||||
|
for _, monster := range monsters {
|
||||||
|
ms.monsters[monster.ID] = monster
|
||||||
|
if monster.ID > ms.maxID {
|
||||||
|
ms.maxID = monster.ID
|
||||||
|
}
|
||||||
|
ms.byLevel[monster.Level] = append(ms.byLevel[monster.Level], monster)
|
||||||
|
ms.byImmunity[monster.Immune] = append(ms.byImmunity[monster.Immune], monster)
|
||||||
|
ms.allByLevel = append(ms.allByLevel, monster)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort all indices
|
||||||
|
sort.Slice(ms.allByLevel, func(i, j int) bool {
|
||||||
|
if ms.allByLevel[i].Level == ms.allByLevel[j].Level {
|
||||||
|
return ms.allByLevel[i].ID < ms.allByLevel[j].ID
|
||||||
|
}
|
||||||
|
return ms.allByLevel[i].Level < ms.allByLevel[j].Level
|
||||||
|
})
|
||||||
|
|
||||||
|
for level := range ms.byLevel {
|
||||||
|
sort.Slice(ms.byLevel[level], func(i, j int) bool {
|
||||||
|
return ms.byLevel[level][i].ID < ms.byLevel[level][j].ID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for immunity := range ms.byImmunity {
|
||||||
|
sort.Slice(ms.byImmunity[immunity], func(i, j int) bool {
|
||||||
|
if ms.byImmunity[immunity][i].Level == ms.byImmunity[immunity][j].Level {
|
||||||
|
return ms.byImmunity[immunity][i].ID < ms.byImmunity[immunity][j].ID
|
||||||
|
}
|
||||||
|
return ms.byImmunity[immunity][i].Level < ms.byImmunity[immunity][j].Level
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveToJSON saves monster data to a JSON file
|
||||||
|
func (ms *MonsterStore) SaveToJSON(filename string) error {
|
||||||
|
ms.mu.RLock()
|
||||||
|
defer ms.mu.RUnlock()
|
||||||
|
|
||||||
|
monsters := make([]*Monster, 0, len(ms.monsters))
|
||||||
|
for _, monster := range ms.monsters {
|
||||||
|
monsters = append(monsters, monster)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by ID for consistent output
|
||||||
|
sort.Slice(monsters, func(i, j int) bool {
|
||||||
|
return monsters[i].ID < monsters[j].ID
|
||||||
|
})
|
||||||
|
|
||||||
|
data, err := json.MarshalIndent(monsters, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal monsters to JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(filename, data, 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write monsters JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findMonstersDataPath finds the monsters.json file relative to the current working directory
|
||||||
|
func findMonstersDataPath() (string, error) {
|
||||||
|
// Try current directory first (cwd/data/monsters.json)
|
||||||
|
if _, err := os.Stat("data/monsters.json"); err == nil {
|
||||||
|
return "data/monsters.json", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk up directories to find the data folder
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
dataPath := filepath.Join(dir, "data", "monsters.json")
|
||||||
|
if _, err := os.Stat(dataPath); err == nil {
|
||||||
|
return dataPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parent := filepath.Dir(dir)
|
||||||
|
if parent == dir {
|
||||||
|
break // reached root
|
||||||
|
}
|
||||||
|
dir = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to current directory if not found
|
||||||
|
return "data/monsters.json", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadData loads monster data from JSON file, or initializes with default data
|
||||||
|
func LoadData() error {
|
||||||
|
store := GetStore()
|
||||||
|
|
||||||
|
dataPath, err := findMonstersDataPath()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find monsters data path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := store.LoadFromJSON(dataPath); err != nil {
|
||||||
|
// If JSON doesn't exist, initialize with default monsters
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
fmt.Println("No existing monster data found, initializing with defaults...")
|
||||||
|
if err := initializeDefaultMonsters(); err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize default monsters: %w", err)
|
||||||
|
}
|
||||||
|
// Save the default data
|
||||||
|
if err := SaveData(); err != nil {
|
||||||
|
return fmt.Errorf("failed to save default monster data: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Initialized %d default monsters\n", len(store.monsters))
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("failed to load from JSON: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Loaded %d monsters from JSON\n", len(store.monsters))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializeDefaultMonsters creates the default monster set
|
||||||
|
func initializeDefaultMonsters() error {
|
||||||
|
store := GetStore()
|
||||||
|
|
||||||
|
// Default monsters from the original SQL data
|
||||||
|
defaultMonsters := []*Monster{
|
||||||
|
{ID: 1, Name: "Blue Slime", MaxHP: 4, MaxDmg: 3, Armor: 1, Level: 1, MaxExp: 1, MaxGold: 1, Immune: ImmuneNone},
|
||||||
|
{ID: 2, Name: "Red Slime", MaxHP: 6, MaxDmg: 5, Armor: 1, Level: 1, MaxExp: 2, MaxGold: 1, Immune: ImmuneNone},
|
||||||
|
{ID: 3, Name: "Critter", MaxHP: 6, MaxDmg: 5, Armor: 2, Level: 1, MaxExp: 4, MaxGold: 2, Immune: ImmuneNone},
|
||||||
|
{ID: 4, Name: "Creature", MaxHP: 10, MaxDmg: 8, Armor: 2, Level: 2, MaxExp: 4, MaxGold: 2, Immune: ImmuneNone},
|
||||||
|
{ID: 5, Name: "Shadow", MaxHP: 10, MaxDmg: 9, Armor: 3, Level: 2, MaxExp: 6, MaxGold: 2, Immune: ImmuneHurt},
|
||||||
|
{ID: 6, Name: "Drake", MaxHP: 11, MaxDmg: 10, Armor: 3, Level: 2, MaxExp: 8, MaxGold: 3, Immune: ImmuneNone},
|
||||||
|
{ID: 7, Name: "Shade", MaxHP: 12, MaxDmg: 10, Armor: 3, Level: 3, MaxExp: 10, MaxGold: 3, Immune: ImmuneHurt},
|
||||||
|
{ID: 8, Name: "Drakelor", MaxHP: 14, MaxDmg: 12, Armor: 4, Level: 3, MaxExp: 10, MaxGold: 3, Immune: ImmuneNone},
|
||||||
|
{ID: 9, Name: "Silver Slime", MaxHP: 15, MaxDmg: 100, Armor: 200, Level: 30, MaxExp: 15, MaxGold: 1000, Immune: ImmuneSleep},
|
||||||
|
{ID: 10, Name: "Scamp", MaxHP: 16, MaxDmg: 13, Armor: 5, Level: 4, MaxExp: 15, MaxGold: 5, Immune: ImmuneNone},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, monster := range defaultMonsters {
|
||||||
|
store.AddMonster(monster)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveData saves monster data to JSON file
|
||||||
|
func SaveData() error {
|
||||||
|
store := GetStore()
|
||||||
|
|
||||||
|
dataPath, err := findMonstersDataPath()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find monsters data path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure data directory exists
|
||||||
|
dataDir := filepath.Dir(dataPath)
|
||||||
|
if err := os.MkdirAll(dataDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create data directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := store.SaveToJSON(dataPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to save monsters to JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Saved %d monsters to JSON\n", len(store.monsters))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves a monster by ID - O(1) lookup
|
||||||
|
func Find(id int) (*Monster, error) {
|
||||||
|
store := GetStore()
|
||||||
|
store.mu.RLock()
|
||||||
|
defer store.mu.RUnlock()
|
||||||
|
|
||||||
|
monster, exists := store.monsters[id]
|
||||||
|
if !exists {
|
||||||
return nil, fmt.Errorf("monster with ID %d not found", id)
|
return nil, fmt.Errorf("monster with ID %d not found", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return monster, nil
|
return monster, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves all monsters
|
// Retrieves all monsters - O(1) lookup (returns pre-sorted slice)
|
||||||
func All() ([]*Monster, error) {
|
func All() ([]*Monster, error) {
|
||||||
var monsters []*Monster
|
store := GetStore()
|
||||||
|
store.mu.RLock()
|
||||||
|
defer store.mu.RUnlock()
|
||||||
|
|
||||||
query := `SELECT ` + monsterColumns() + ` FROM monsters ORDER BY level, id`
|
// Return a copy of the slice to prevent external modifications
|
||||||
|
result := make([]*Monster, len(store.allByLevel))
|
||||||
err := database.Query(query, func(stmt *sqlite.Stmt) error {
|
copy(result, store.allByLevel)
|
||||||
monster := scanMonster(stmt)
|
return result, nil
|
||||||
monsters = append(monsters, monster)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to retrieve all monsters: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return monsters, nil
|
// Retrieves monsters by level - O(1) lookup
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieves monsters by level
|
|
||||||
func ByLevel(level int) ([]*Monster, error) {
|
func ByLevel(level int) ([]*Monster, error) {
|
||||||
var monsters []*Monster
|
store := GetStore()
|
||||||
|
store.mu.RLock()
|
||||||
|
defer store.mu.RUnlock()
|
||||||
|
|
||||||
query := `SELECT ` + monsterColumns() + ` FROM monsters WHERE level = ? ORDER BY id`
|
monsters, exists := store.byLevel[level]
|
||||||
|
if !exists {
|
||||||
err := database.Query(query, func(stmt *sqlite.Stmt) error {
|
return []*Monster{}, nil
|
||||||
monster := scanMonster(stmt)
|
|
||||||
monsters = append(monsters, monster)
|
|
||||||
return nil
|
|
||||||
}, level)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to retrieve monsters by level: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return monsters, nil
|
// Return a copy of the slice to prevent external modifications
|
||||||
|
result := make([]*Monster, len(monsters))
|
||||||
|
copy(result, monsters)
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves monsters within a level range (inclusive)
|
// Retrieves monsters within a level range (inclusive) - O(k) where k is result size
|
||||||
func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) {
|
func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) {
|
||||||
var monsters []*Monster
|
store := GetStore()
|
||||||
|
store.mu.RLock()
|
||||||
|
defer store.mu.RUnlock()
|
||||||
|
|
||||||
query := `SELECT ` + monsterColumns() + ` FROM monsters WHERE level BETWEEN ? AND ? ORDER BY level, id`
|
var result []*Monster
|
||||||
|
for level := minLevel; level <= maxLevel; level++ {
|
||||||
err := database.Query(query, func(stmt *sqlite.Stmt) error {
|
if monsters, exists := store.byLevel[level]; exists {
|
||||||
monster := scanMonster(stmt)
|
result = append(result, monsters...)
|
||||||
monsters = append(monsters, monster)
|
}
|
||||||
return nil
|
|
||||||
}, minLevel, maxLevel)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to retrieve monsters by level range: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return monsters, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves monsters by immunity type
|
// Retrieves monsters by immunity type - O(1) lookup
|
||||||
func ByImmunity(immunityType int) ([]*Monster, error) {
|
func ByImmunity(immunityType int) ([]*Monster, error) {
|
||||||
var monsters []*Monster
|
store := GetStore()
|
||||||
|
store.mu.RLock()
|
||||||
|
defer store.mu.RUnlock()
|
||||||
|
|
||||||
query := `SELECT ` + monsterColumns() + ` FROM monsters WHERE immune = ? ORDER BY level, id`
|
monsters, exists := store.byImmunity[immunityType]
|
||||||
|
if !exists {
|
||||||
err := database.Query(query, func(stmt *sqlite.Stmt) error {
|
return []*Monster{}, nil
|
||||||
monster := scanMonster(stmt)
|
|
||||||
monsters = append(monsters, monster)
|
|
||||||
return nil
|
|
||||||
}, immunityType)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to retrieve monsters by immunity: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return monsters, nil
|
// Return a copy of the slice to prevent external modifications
|
||||||
|
result := make([]*Monster, len(monsters))
|
||||||
|
copy(result, monsters)
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saves a new monster to the database and sets the ID
|
// Saves a new monster to the in-memory store and sets the ID
|
||||||
func (m *Monster) Insert() error {
|
func (m *Monster) Insert() error {
|
||||||
columns := `name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune`
|
store := GetStore()
|
||||||
values := []any{m.Name, m.MaxHP, m.MaxDmg, m.Armor, m.Level, m.MaxExp, m.MaxGold, m.Immune}
|
|
||||||
return database.Insert(m, columns, values...)
|
// Assign new ID if not set
|
||||||
|
if m.ID == 0 {
|
||||||
|
m.ID = store.GetNextID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to store
|
||||||
|
store.AddMonster(m)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the monster is immune to Hurt spells
|
// Returns true if the monster is immune to Hurt spells
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"dk/internal/auth"
|
"dk/internal/auth"
|
||||||
"dk/internal/database"
|
"dk/internal/database"
|
||||||
"dk/internal/middleware"
|
"dk/internal/middleware"
|
||||||
|
"dk/internal/monsters"
|
||||||
"dk/internal/router"
|
"dk/internal/router"
|
||||||
"dk/internal/routes"
|
"dk/internal/routes"
|
||||||
"dk/internal/template"
|
"dk/internal/template"
|
||||||
@ -32,6 +33,11 @@ func Start(port string) error {
|
|||||||
}
|
}
|
||||||
defer database.Close()
|
defer database.Close()
|
||||||
|
|
||||||
|
// Load monster data into memory
|
||||||
|
if err := monsters.LoadData(); err != nil {
|
||||||
|
return fmt.Errorf("failed to load monster data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
auth.Init("sessions.json") // Initialize auth.Manager
|
auth.Init("sessions.json") // Initialize auth.Manager
|
||||||
|
|
||||||
r := router.New()
|
r := router.New()
|
||||||
@ -96,6 +102,12 @@ func Start(port string) error {
|
|||||||
<-c
|
<-c
|
||||||
log.Println("Received shutdown signal, shutting down gracefully...")
|
log.Println("Received shutdown signal, shutting down gracefully...")
|
||||||
|
|
||||||
|
// Save monster data before shutdown
|
||||||
|
log.Println("Saving monster data...")
|
||||||
|
if err := monsters.SaveData(); err != nil {
|
||||||
|
log.Printf("Error saving monster data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Save sessions before shutdown
|
// Save sessions before shutdown
|
||||||
log.Println("Saving sessions...")
|
log.Println("Saving sessions...")
|
||||||
if err := auth.Manager.Close(); err != nil {
|
if err := auth.Manager.Close(); err != nil {
|
||||||
|
34
test_load_data.go
Normal file
34
test_load_data.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"dk/internal/monsters"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Testing LoadData() function...")
|
||||||
|
|
||||||
|
err := monsters.LoadData()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("LoadData() failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that we can find a monster
|
||||||
|
monster, err := monsters.Find(1)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Find(1) failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Successfully loaded data! Found monster: %s (Level %d)\n", monster.Name, monster.Level)
|
||||||
|
|
||||||
|
// Test getting all monsters
|
||||||
|
all, err := monsters.All()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("All() failed: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Total monsters loaded: %d\n", len(all))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user