From 75a1927d3a6cea2a5d297af1f1ade896c2b1898f Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Fri, 22 Aug 2025 07:47:01 -0500 Subject: [PATCH] finish migration back to sqlite, update routes/actions et cetera --- data/babble.json | 1 - data/control.json | 11 - data/dk.db | Bin 0 -> 73728 bytes data/drops.json | 226 ----- data/fights.json | 1108 ---------------------- data/forum.json | 1 - data/items.json | 266 ------ data/monsters.json | 1663 --------------------------------- data/news.json | 1 - data/spells.json | 135 --- data/towns.json | 82 -- database.sql | 85 +- go.mod | 3 +- go.sum | 28 +- internal/actions/fight.go | 309 +++--- internal/actions/move.go | 2 +- internal/actions/user_item.go | 83 +- internal/database/wrapper.go | 22 + internal/routes/auth.go | 31 +- internal/routes/fight.go | 137 +-- internal/routes/index.go | 34 +- internal/routes/town.go | 60 +- main.go | 7 + 23 files changed, 557 insertions(+), 3738 deletions(-) delete mode 100644 data/babble.json delete mode 100644 data/control.json create mode 100644 data/dk.db delete mode 100644 data/drops.json delete mode 100644 data/fights.json delete mode 100644 data/forum.json delete mode 100644 data/items.json delete mode 100644 data/monsters.json delete mode 100644 data/news.json delete mode 100644 data/spells.json delete mode 100644 data/towns.json diff --git a/data/babble.json b/data/babble.json deleted file mode 100644 index 0637a08..0000000 --- a/data/babble.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/data/control.json b/data/control.json deleted file mode 100644 index c71d452..0000000 --- a/data/control.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "id": 1, - "world_size": 200, - "open": 1, - "admin_email": "", - "class_1_name": "Mage", - "class_2_name": "Warrior", - "class_3_name": "Paladin" - } -] \ No newline at end of file diff --git a/data/dk.db b/data/dk.db new file mode 100644 index 0000000000000000000000000000000000000000..09be7ffa9c2bc9c79a80b9d4dccbf46f455cce1f GIT binary patch literal 73728 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 literal 0 HcmV?d00001 diff --git a/data/drops.json b/data/drops.json deleted file mode 100644 index 8164b75..0000000 --- a/data/drops.json +++ /dev/null @@ -1,226 +0,0 @@ -[ - { - "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" - } -] \ No newline at end of file diff --git a/data/fights.json b/data/fights.json deleted file mode 100644 index e38fef9..0000000 --- a/data/fights.json +++ /dev/null @@ -1,1108 +0,0 @@ -[ - { - "id": 1, - "user_id": 1, - "monster_id": 2, - "monster_hp": 0, - "monster_max_hp": 6, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": true, - "turn": 7, - "ran_away": false, - "victory": true, - "won": true, - "reward_gold": 0, - "reward_exp": 0, - "actions": [ - { - "t": 2 - }, - { - "t": 1, - "d": 1 - }, - { - "t": 1, - "d": 1 - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 11, - "n": "Red Slime" - } - ], - "created": 1755264713, - "updated": 1755270326 - }, - { - "id": 2, - "user_id": 1, - "monster_id": 5, - "monster_hp": 7, - "monster_max_hp": 10, - "monster_sleep": 0, - "monster_immune": 1, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": true, - "turn": 3, - "ran_away": false, - "victory": true, - "won": false, - "reward_gold": 0, - "reward_exp": 0, - "actions": [ - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 3, - "n": "Shadow" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 2, - "n": "Shadow" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 3, - "n": "Shadow" - } - ], - "created": 1755270342, - "updated": 1755270352 - }, - { - "id": 3, - "user_id": 1, - "monster_id": 5, - "monster_hp": 6, - "monster_max_hp": 10, - "monster_sleep": 0, - "monster_immune": 1, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": true, - "turn": 4, - "ran_away": false, - "victory": true, - "won": false, - "reward_gold": 0, - "reward_exp": 0, - "actions": [ - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 2, - "n": "Shadow" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 3, - "n": "Shadow" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 3, - "n": "Shadow" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 3, - "n": "Shadow" - } - ], - "created": 1755270581, - "updated": 1755270585 - }, - { - "id": 4, - "user_id": 1, - "monster_id": 2, - "monster_hp": 0, - "monster_max_hp": 6, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": false, - "turn": 6, - "ran_away": false, - "victory": true, - "won": true, - "reward_gold": 0, - "reward_exp": 0, - "actions": [ - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 11, - "n": "Red Slime" - } - ], - "created": 1755270614, - "updated": 1755270620 - }, - { - "id": 5, - "user_id": 1, - "monster_id": 5, - "monster_hp": 10, - "monster_max_hp": 10, - "monster_sleep": 0, - "monster_immune": 1, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": true, - "turn": 1, - "ran_away": false, - "victory": false, - "won": false, - "reward_gold": 0, - "reward_exp": 0, - "actions": [], - "created": 1755608716, - "updated": 1755608716 - }, - { - "id": 6, - "user_id": 1, - "monster_id": 1, - "monster_hp": 0, - "monster_max_hp": 4, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": true, - "turn": 4, - "ran_away": false, - "victory": true, - "won": true, - "reward_gold": 1, - "reward_exp": 1, - "actions": [ - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Blue Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Blue Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Blue Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 11, - "n": "Blue Slime" - } - ], - "created": 1755274346, - "updated": 1755274352 - }, - { - "id": 7, - "user_id": 1, - "monster_id": 6, - "monster_hp": 10, - "monster_max_hp": 11, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": true, - "turn": 1, - "ran_away": false, - "victory": true, - "won": false, - "reward_gold": 0, - "reward_exp": 0, - "actions": [ - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 2, - "n": "Drake" - } - ], - "created": 1755274391, - "updated": 1755274393 - }, - { - "id": 8, - "user_id": 1, - "monster_id": 5, - "monster_hp": 6, - "monster_max_hp": 10, - "monster_sleep": 0, - "monster_immune": 1, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": false, - "turn": 4, - "ran_away": false, - "victory": true, - "won": false, - "reward_gold": 0, - "reward_exp": 0, - "actions": [ - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 2, - "n": "Shadow" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 3, - "n": "Shadow" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 3, - "n": "Shadow" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 3, - "n": "Shadow" - } - ], - "created": 1755274646, - "updated": 1755274652 - }, - { - "id": 9, - "user_id": 1, - "monster_id": 2, - "monster_hp": 0, - "monster_max_hp": 6, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": true, - "turn": 6, - "ran_away": false, - "victory": true, - "won": true, - "reward_gold": 1, - "reward_exp": 2, - "actions": [ - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 11, - "n": "Red Slime" - } - ], - "created": 1755274719, - "updated": 1755274725 - }, - { - "id": 10, - "user_id": 1, - "monster_id": 2, - "monster_hp": 0, - "monster_max_hp": 6, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": false, - "turn": 6, - "ran_away": false, - "victory": true, - "won": true, - "reward_gold": 1, - "reward_exp": 2, - "actions": [ - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 11, - "n": "Red Slime" - } - ], - "created": 1755274741, - "updated": 1755274746 - }, - { - "id": 11, - "user_id": 1, - "monster_id": 8, - "monster_hp": 6, - "monster_max_hp": 14, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": false, - "turn": 10, - "ran_away": false, - "victory": true, - "won": false, - "reward_gold": 0, - "reward_exp": 0, - "actions": [ - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Drakelor" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Drakelor" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Drakelor" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Drakelor" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Drakelor" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Drakelor" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Drakelor" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Drakelor" - }, - { - "t": 7, - "n": "You failed to run away!" - }, - { - "t": 8, - "d": 1, - "n": "Drakelor" - }, - { - "t": 7, - "n": "You failed to run away!" - }, - { - "t": 8, - "d": 1, - "n": "Drakelor" - } - ], - "created": 1755274758, - "updated": 1755274768 - }, - { - "id": 12, - "user_id": 1, - "monster_id": 2, - "monster_hp": 0, - "monster_max_hp": 6, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": true, - "turn": 6, - "ran_away": false, - "victory": true, - "won": true, - "reward_gold": 1, - "reward_exp": 1, - "actions": [ - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Red Slime" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 11, - "n": "Red Slime" - } - ], - "created": 1755274777, - "updated": 1755274782 - }, - { - "id": 13, - "user_id": 1, - "monster_id": 8, - "monster_hp": 4, - "monster_max_hp": 14, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": true, - "turn": 10, - "ran_away": false, - "victory": true, - "won": false, - "reward_gold": 0, - "reward_exp": 0, - "actions": [], - "created": 1755222893, - "updated": 1755222893 - }, - { - "id": 14, - "user_id": 1, - "monster_id": 6, - "monster_hp": 0, - "monster_max_hp": 11, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": false, - "turn": 6, - "ran_away": false, - "victory": true, - "won": true, - "reward_gold": 2, - "reward_exp": 6, - "actions": [ - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Drake" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Drake" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 2, - "n": "Drake" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 2, - "n": "Drake" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Drake" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 11, - "n": "Drake" - } - ], - "created": 1755788992, - "updated": 1755788999 - }, - { - "id": 15, - "user_id": 1, - "monster_id": 5, - "monster_hp": 0, - "monster_max_hp": 10, - "monster_sleep": 0, - "monster_immune": 1, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": false, - "turn": 6, - "ran_away": false, - "victory": true, - "won": true, - "reward_gold": 1, - "reward_exp": 5, - "actions": [ - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Shadow" - }, - { - "t": 1, - "d": 1 - }, - { - "t": 8, - "d": 1, - "n": "Shadow" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Shadow" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Shadow" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Shadow" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 11, - "n": "Shadow" - } - ], - "created": 1755789058, - "updated": 1755789064 - }, - { - "id": 16, - "user_id": 1, - "monster_id": 1, - "monster_hp": 0, - "monster_max_hp": 4, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": true, - "turn": 2, - "ran_away": false, - "victory": true, - "won": true, - "reward_gold": 0, - "reward_exp": 1, - "actions": [ - { - "t": 1, - "d": 3 - }, - { - "t": 8, - "d": 1, - "n": "Blue Slime" - }, - { - "t": 1, - "d": 3 - }, - { - "t": 11, - "n": "Blue Slime" - } - ], - "created": 1755789095, - "updated": 1755789098 - }, - { - "id": 17, - "user_id": 1, - "monster_id": 7, - "monster_hp": 0, - "monster_max_hp": 12, - "monster_sleep": 0, - "monster_immune": 1, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": false, - "turn": 6, - "ran_away": false, - "victory": true, - "won": true, - "reward_gold": 3, - "reward_exp": 9, - "actions": [ - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Shade" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Shade" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Shade" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Shade" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Shade" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 11, - "n": "Shade" - } - ], - "created": 1755806329, - "updated": 1755806335 - }, - { - "id": 18, - "user_id": 1, - "monster_id": 4, - "monster_hp": 4, - "monster_max_hp": 10, - "monster_sleep": 0, - "monster_immune": 0, - "uber_damage": 0, - "uber_defense": 0, - "first_strike": false, - "turn": 5, - "ran_away": false, - "victory": true, - "won": false, - "reward_gold": 0, - "reward_exp": 0, - "actions": [ - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Creature" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Creature" - }, - { - "t": 1, - "d": 2 - }, - { - "t": 8, - "d": 1, - "n": "Creature" - }, - { - "t": 7, - "n": "You failed to run away!" - }, - { - "t": 8, - "d": 1, - "n": "Creature" - }, - { - "t": 7, - "n": "You failed to run away!" - }, - { - "t": 8, - "d": 1, - "n": "Creature" - } - ], - "created": 1755806697, - "updated": 1755806703 - } -] \ No newline at end of file diff --git a/data/forum.json b/data/forum.json deleted file mode 100644 index 0637a08..0000000 --- a/data/forum.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/data/items.json b/data/items.json deleted file mode 100644 index e98b5b7..0000000 --- a/data/items.json +++ /dev/null @@ -1,266 +0,0 @@ -[ - { - "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" - } -] \ No newline at end of file diff --git a/data/monsters.json b/data/monsters.json deleted file mode 100644 index a2c2832..0000000 --- a/data/monsters.json +++ /dev/null @@ -1,1663 +0,0 @@ -[ - { - "id": 1, - "name": "Blue Slime", - "max_hp": 4, - "max_dmg": 3, - "armor": 1, - "level": 1, - "max_exp": 1, - "max_gold": 1, - "immune": 0 - }, - { - "id": 2, - "name": "Red Slime", - "max_hp": 6, - "max_dmg": 5, - "armor": 1, - "level": 1, - "max_exp": 2, - "max_gold": 1, - "immune": 0 - }, - { - "id": 3, - "name": "Critter", - "max_hp": 6, - "max_dmg": 5, - "armor": 2, - "level": 1, - "max_exp": 4, - "max_gold": 2, - "immune": 0 - }, - { - "id": 4, - "name": "Creature", - "max_hp": 10, - "max_dmg": 8, - "armor": 2, - "level": 2, - "max_exp": 4, - "max_gold": 2, - "immune": 0 - }, - { - "id": 5, - "name": "Shadow", - "max_hp": 10, - "max_dmg": 9, - "armor": 3, - "level": 2, - "max_exp": 6, - "max_gold": 2, - "immune": 1 - }, - { - "id": 6, - "name": "Drake", - "max_hp": 11, - "max_dmg": 10, - "armor": 3, - "level": 2, - "max_exp": 8, - "max_gold": 3, - "immune": 0 - }, - { - "id": 7, - "name": "Shade", - "max_hp": 12, - "max_dmg": 10, - "armor": 3, - "level": 3, - "max_exp": 10, - "max_gold": 3, - "immune": 1 - }, - { - "id": 8, - "name": "Drakelor", - "max_hp": 14, - "max_dmg": 12, - "armor": 4, - "level": 3, - "max_exp": 10, - "max_gold": 3, - "immune": 0 - }, - { - "id": 9, - "name": "Silver Slime", - "max_hp": 15, - "max_dmg": 100, - "armor": 200, - "level": 30, - "max_exp": 15, - "max_gold": 1000, - "immune": 2 - }, - { - "id": 10, - "name": "Scamp", - "max_hp": 16, - "max_dmg": 13, - "armor": 5, - "level": 4, - "max_exp": 15, - "max_gold": 5, - "immune": 0 - }, - { - "id": 11, - "name": "Raven", - "max_hp": 16, - "max_dmg": 13, - "armor": 5, - "level": 4, - "max_exp": 18, - "max_gold": 6, - "immune": 0 - }, - { - "id": 12, - "name": "Scorpion", - "max_hp": 18, - "max_dmg": 14, - "armor": 6, - "level": 5, - "max_exp": 20, - "max_gold": 7, - "immune": 0 - }, - { - "id": 13, - "name": "Illusion", - "max_hp": 20, - "max_dmg": 15, - "armor": 6, - "level": 5, - "max_exp": 20, - "max_gold": 7, - "immune": 1 - }, - { - "id": 14, - "name": "Nightshade", - "max_hp": 22, - "max_dmg": 16, - "armor": 6, - "level": 6, - "max_exp": 24, - "max_gold": 8, - "immune": 0 - }, - { - "id": 15, - "name": "Drakemal", - "max_hp": 22, - "max_dmg": 18, - "armor": 7, - "level": 6, - "max_exp": 24, - "max_gold": 8, - "immune": 0 - }, - { - "id": 16, - "name": "Shadow Raven", - "max_hp": 24, - "max_dmg": 18, - "armor": 7, - "level": 6, - "max_exp": 26, - "max_gold": 9, - "immune": 1 - }, - { - "id": 17, - "name": "Ghost", - "max_hp": 24, - "max_dmg": 20, - "armor": 8, - "level": 6, - "max_exp": 28, - "max_gold": 9, - "immune": 0 - }, - { - "id": 18, - "name": "Frost Raven", - "max_hp": 26, - "max_dmg": 20, - "armor": 8, - "level": 7, - "max_exp": 30, - "max_gold": 10, - "immune": 0 - }, - { - "id": 19, - "name": "Rogue Scorpion", - "max_hp": 28, - "max_dmg": 22, - "armor": 9, - "level": 7, - "max_exp": 32, - "max_gold": 11, - "immune": 0 - }, - { - "id": 20, - "name": "Ghoul", - "max_hp": 29, - "max_dmg": 24, - "armor": 9, - "level": 7, - "max_exp": 34, - "max_gold": 11, - "immune": 0 - }, - { - "id": 21, - "name": "Magician", - "max_hp": 30, - "max_dmg": 24, - "armor": 10, - "level": 8, - "max_exp": 36, - "max_gold": 12, - "immune": 0 - }, - { - "id": 22, - "name": "Rogue", - "max_hp": 30, - "max_dmg": 25, - "armor": 12, - "level": 8, - "max_exp": 40, - "max_gold": 13, - "immune": 0 - }, - { - "id": 23, - "name": "Drakefin", - "max_hp": 32, - "max_dmg": 26, - "armor": 12, - "level": 8, - "max_exp": 40, - "max_gold": 13, - "immune": 0 - }, - { - "id": 24, - "name": "Shimmer", - "max_hp": 32, - "max_dmg": 26, - "armor": 14, - "level": 8, - "max_exp": 45, - "max_gold": 15, - "immune": 1 - }, - { - "id": 25, - "name": "Fire Raven", - "max_hp": 34, - "max_dmg": 28, - "armor": 14, - "level": 9, - "max_exp": 45, - "max_gold": 15, - "immune": 0 - }, - { - "id": 26, - "name": "Dybbuk", - "max_hp": 34, - "max_dmg": 28, - "armor": 14, - "level": 9, - "max_exp": 50, - "max_gold": 17, - "immune": 0 - }, - { - "id": 27, - "name": "Knave", - "max_hp": 36, - "max_dmg": 30, - "armor": 15, - "level": 9, - "max_exp": 52, - "max_gold": 17, - "immune": 0 - }, - { - "id": 28, - "name": "Goblin", - "max_hp": 36, - "max_dmg": 30, - "armor": 15, - "level": 10, - "max_exp": 54, - "max_gold": 18, - "immune": 0 - }, - { - "id": 29, - "name": "Skeleton", - "max_hp": 38, - "max_dmg": 30, - "armor": 18, - "level": 10, - "max_exp": 58, - "max_gold": 19, - "immune": 0 - }, - { - "id": 30, - "name": "Dark Slime", - "max_hp": 38, - "max_dmg": 32, - "armor": 18, - "level": 10, - "max_exp": 62, - "max_gold": 21, - "immune": 0 - }, - { - "id": 31, - "name": "Silver Scorpion", - "max_hp": 30, - "max_dmg": 160, - "armor": 350, - "level": 40, - "max_exp": 63, - "max_gold": 2000, - "immune": 2 - }, - { - "id": 32, - "name": "Mirage", - "max_hp": 40, - "max_dmg": 32, - "armor": 20, - "level": 11, - "max_exp": 64, - "max_gold": 21, - "immune": 1 - }, - { - "id": 33, - "name": "Sorceror", - "max_hp": 41, - "max_dmg": 33, - "armor": 22, - "level": 11, - "max_exp": 68, - "max_gold": 23, - "immune": 0 - }, - { - "id": 34, - "name": "Imp", - "max_hp": 42, - "max_dmg": 34, - "armor": 22, - "level": 12, - "max_exp": 70, - "max_gold": 23, - "immune": 0 - }, - { - "id": 35, - "name": "Nymph", - "max_hp": 43, - "max_dmg": 35, - "armor": 22, - "level": 12, - "max_exp": 70, - "max_gold": 23, - "immune": 0 - }, - { - "id": 36, - "name": "Scoundrel", - "max_hp": 43, - "max_dmg": 35, - "armor": 22, - "level": 12, - "max_exp": 75, - "max_gold": 25, - "immune": 0 - }, - { - "id": 37, - "name": "Megaskeleton", - "max_hp": 44, - "max_dmg": 36, - "armor": 24, - "level": 13, - "max_exp": 78, - "max_gold": 26, - "immune": 0 - }, - { - "id": 38, - "name": "Grey Wolf", - "max_hp": 44, - "max_dmg": 36, - "armor": 24, - "level": 13, - "max_exp": 82, - "max_gold": 27, - "immune": 0 - }, - { - "id": 39, - "name": "Phantom", - "max_hp": 46, - "max_dmg": 38, - "armor": 24, - "level": 14, - "max_exp": 85, - "max_gold": 28, - "immune": 1 - }, - { - "id": 40, - "name": "Specter", - "max_hp": 46, - "max_dmg": 38, - "armor": 24, - "level": 14, - "max_exp": 90, - "max_gold": 30, - "immune": 0 - }, - { - "id": 41, - "name": "Dark Scorpion", - "max_hp": 48, - "max_dmg": 40, - "armor": 26, - "level": 15, - "max_exp": 95, - "max_gold": 32, - "immune": 1 - }, - { - "id": 42, - "name": "Warlock", - "max_hp": 48, - "max_dmg": 40, - "armor": 26, - "level": 15, - "max_exp": 100, - "max_gold": 33, - "immune": 1 - }, - { - "id": 43, - "name": "Orc", - "max_hp": 49, - "max_dmg": 42, - "armor": 28, - "level": 15, - "max_exp": 104, - "max_gold": 35, - "immune": 0 - }, - { - "id": 44, - "name": "Sylph", - "max_hp": 49, - "max_dmg": 42, - "armor": 28, - "level": 15, - "max_exp": 106, - "max_gold": 35, - "immune": 0 - }, - { - "id": 45, - "name": "Wraith", - "max_hp": 50, - "max_dmg": 45, - "armor": 30, - "level": 16, - "max_exp": 108, - "max_gold": 36, - "immune": 0 - }, - { - "id": 46, - "name": "Hellion", - "max_hp": 50, - "max_dmg": 45, - "armor": 30, - "level": 16, - "max_exp": 110, - "max_gold": 37, - "immune": 0 - }, - { - "id": 47, - "name": "Bandit", - "max_hp": 52, - "max_dmg": 45, - "armor": 30, - "level": 16, - "max_exp": 114, - "max_gold": 38, - "immune": 0 - }, - { - "id": 48, - "name": "Ultraskeleton", - "max_hp": 52, - "max_dmg": 46, - "armor": 32, - "level": 16, - "max_exp": 116, - "max_gold": 39, - "immune": 0 - }, - { - "id": 49, - "name": "Dark Wolf", - "max_hp": 54, - "max_dmg": 47, - "armor": 36, - "level": 17, - "max_exp": 120, - "max_gold": 40, - "immune": 1 - }, - { - "id": 50, - "name": "Troll", - "max_hp": 56, - "max_dmg": 48, - "armor": 36, - "level": 17, - "max_exp": 120, - "max_gold": 40, - "immune": 0 - }, - { - "id": 51, - "name": "Werewolf", - "max_hp": 56, - "max_dmg": 48, - "armor": 38, - "level": 17, - "max_exp": 124, - "max_gold": 41, - "immune": 0 - }, - { - "id": 52, - "name": "Hellcat", - "max_hp": 58, - "max_dmg": 50, - "armor": 38, - "level": 18, - "max_exp": 128, - "max_gold": 43, - "immune": 0 - }, - { - "id": 53, - "name": "Spirit", - "max_hp": 58, - "max_dmg": 50, - "armor": 38, - "level": 18, - "max_exp": 132, - "max_gold": 44, - "immune": 0 - }, - { - "id": 54, - "name": "Nisse", - "max_hp": 60, - "max_dmg": 52, - "armor": 40, - "level": 19, - "max_exp": 132, - "max_gold": 44, - "immune": 0 - }, - { - "id": 55, - "name": "Dawk", - "max_hp": 60, - "max_dmg": 54, - "armor": 40, - "level": 19, - "max_exp": 136, - "max_gold": 45, - "immune": 0 - }, - { - "id": 56, - "name": "Figment", - "max_hp": 64, - "max_dmg": 55, - "armor": 42, - "level": 19, - "max_exp": 140, - "max_gold": 47, - "immune": 1 - }, - { - "id": 57, - "name": "Hellhound", - "max_hp": 66, - "max_dmg": 56, - "armor": 44, - "level": 20, - "max_exp": 140, - "max_gold": 47, - "immune": 0 - }, - { - "id": 58, - "name": "Wizard", - "max_hp": 66, - "max_dmg": 56, - "armor": 44, - "level": 20, - "max_exp": 144, - "max_gold": 48, - "immune": 0 - }, - { - "id": 59, - "name": "Uruk", - "max_hp": 68, - "max_dmg": 58, - "armor": 44, - "level": 20, - "max_exp": 146, - "max_gold": 49, - "immune": 0 - }, - { - "id": 60, - "name": "Siren", - "max_hp": 68, - "max_dmg": 400, - "armor": 800, - "level": 50, - "max_exp": 10000, - "max_gold": 50, - "immune": 2 - }, - { - "id": 61, - "name": "Megawraith", - "max_hp": 70, - "max_dmg": 60, - "armor": 46, - "level": 21, - "max_exp": 155, - "max_gold": 52, - "immune": 0 - }, - { - "id": 62, - "name": "Dawkin", - "max_hp": 70, - "max_dmg": 60, - "armor": 46, - "level": 21, - "max_exp": 155, - "max_gold": 52, - "immune": 0 - }, - { - "id": 63, - "name": "Grey Bear", - "max_hp": 70, - "max_dmg": 62, - "armor": 48, - "level": 21, - "max_exp": 160, - "max_gold": 53, - "immune": 0 - }, - { - "id": 64, - "name": "Haunt", - "max_hp": 72, - "max_dmg": 62, - "armor": 48, - "level": 22, - "max_exp": 160, - "max_gold": 53, - "immune": 0 - }, - { - "id": 65, - "name": "Hellbeast", - "max_hp": 74, - "max_dmg": 64, - "armor": 50, - "level": 22, - "max_exp": 165, - "max_gold": 55, - "immune": 0 - }, - { - "id": 66, - "name": "Fear", - "max_hp": 76, - "max_dmg": 66, - "armor": 52, - "level": 23, - "max_exp": 165, - "max_gold": 55, - "immune": 0 - }, - { - "id": 67, - "name": "Beast", - "max_hp": 76, - "max_dmg": 66, - "armor": 52, - "level": 23, - "max_exp": 170, - "max_gold": 57, - "immune": 0 - }, - { - "id": 68, - "name": "Ogre", - "max_hp": 78, - "max_dmg": 68, - "armor": 54, - "level": 23, - "max_exp": 170, - "max_gold": 57, - "immune": 0 - }, - { - "id": 69, - "name": "Dark Bear", - "max_hp": 80, - "max_dmg": 70, - "armor": 56, - "level": 24, - "max_exp": 175, - "max_gold": 58, - "immune": 1 - }, - { - "id": 70, - "name": "Fire", - "max_hp": 80, - "max_dmg": 72, - "armor": 56, - "level": 24, - "max_exp": 175, - "max_gold": 58, - "immune": 0 - }, - { - "id": 71, - "name": "Polgergeist", - "max_hp": 84, - "max_dmg": 74, - "armor": 58, - "level": 25, - "max_exp": 180, - "max_gold": 60, - "immune": 0 - }, - { - "id": 72, - "name": "Fright", - "max_hp": 86, - "max_dmg": 76, - "armor": 58, - "level": 25, - "max_exp": 180, - "max_gold": 60, - "immune": 0 - }, - { - "id": 73, - "name": "Lycan", - "max_hp": 88, - "max_dmg": 78, - "armor": 60, - "level": 25, - "max_exp": 185, - "max_gold": 62, - "immune": 0 - }, - { - "id": 74, - "name": "Terra Elemental", - "max_hp": 88, - "max_dmg": 80, - "armor": 62, - "level": 25, - "max_exp": 185, - "max_gold": 62, - "immune": 1 - }, - { - "id": 75, - "name": "Necromancer", - "max_hp": 90, - "max_dmg": 80, - "armor": 62, - "level": 26, - "max_exp": 190, - "max_gold": 63, - "immune": 0 - }, - { - "id": 76, - "name": "Ultrawraith", - "max_hp": 90, - "max_dmg": 82, - "armor": 64, - "level": 26, - "max_exp": 190, - "max_gold": 63, - "immune": 0 - }, - { - "id": 77, - "name": "Dawkor", - "max_hp": 92, - "max_dmg": 82, - "armor": 64, - "level": 26, - "max_exp": 195, - "max_gold": 65, - "immune": 0 - }, - { - "id": 78, - "name": "Werebear", - "max_hp": 92, - "max_dmg": 84, - "armor": 65, - "level": 26, - "max_exp": 195, - "max_gold": 65, - "immune": 0 - }, - { - "id": 79, - "name": "Brute", - "max_hp": 94, - "max_dmg": 84, - "armor": 65, - "level": 27, - "max_exp": 200, - "max_gold": 67, - "immune": 0 - }, - { - "id": 80, - "name": "Large Beast", - "max_hp": 96, - "max_dmg": 88, - "armor": 66, - "level": 27, - "max_exp": 200, - "max_gold": 67, - "immune": 0 - }, - { - "id": 81, - "name": "Horror", - "max_hp": 96, - "max_dmg": 88, - "armor": 68, - "level": 27, - "max_exp": 210, - "max_gold": 70, - "immune": 0 - }, - { - "id": 82, - "name": "Flame", - "max_hp": 100, - "max_dmg": 90, - "armor": 70, - "level": 28, - "max_exp": 210, - "max_gold": 70, - "immune": 0 - }, - { - "id": 83, - "name": "Lycanthor", - "max_hp": 100, - "max_dmg": 90, - "armor": 70, - "level": 28, - "max_exp": 210, - "max_gold": 70, - "immune": 0 - }, - { - "id": 84, - "name": "Wyrm", - "max_hp": 100, - "max_dmg": 92, - "armor": 72, - "level": 28, - "max_exp": 220, - "max_gold": 73, - "immune": 0 - }, - { - "id": 85, - "name": "Aero Elemental", - "max_hp": 104, - "max_dmg": 94, - "armor": 74, - "level": 29, - "max_exp": 220, - "max_gold": 73, - "immune": 1 - }, - { - "id": 86, - "name": "Dawkare", - "max_hp": 106, - "max_dmg": 96, - "armor": 76, - "level": 29, - "max_exp": 220, - "max_gold": 73, - "immune": 0 - }, - { - "id": 87, - "name": "Large Brute", - "max_hp": 108, - "max_dmg": 98, - "armor": 78, - "level": 29, - "max_exp": 230, - "max_gold": 77, - "immune": 0 - }, - { - "id": 88, - "name": "Frost Wyrm", - "max_hp": 110, - "max_dmg": 100, - "armor": 80, - "level": 30, - "max_exp": 230, - "max_gold": 77, - "immune": 0 - }, - { - "id": 89, - "name": "Knight", - "max_hp": 110, - "max_dmg": 102, - "armor": 80, - "level": 30, - "max_exp": 240, - "max_gold": 80, - "immune": 0 - }, - { - "id": 90, - "name": "Lycanthra", - "max_hp": 112, - "max_dmg": 104, - "armor": 82, - "level": 30, - "max_exp": 240, - "max_gold": 80, - "immune": 0 - }, - { - "id": 91, - "name": "Terror", - "max_hp": 115, - "max_dmg": 108, - "armor": 84, - "level": 31, - "max_exp": 250, - "max_gold": 83, - "immune": 0 - }, - { - "id": 92, - "name": "Blaze", - "max_hp": 118, - "max_dmg": 108, - "armor": 84, - "level": 31, - "max_exp": 250, - "max_gold": 83, - "immune": 0 - }, - { - "id": 93, - "name": "Aqua Elemental", - "max_hp": 120, - "max_dmg": 110, - "armor": 90, - "level": 31, - "max_exp": 260, - "max_gold": 87, - "immune": 1 - }, - { - "id": 94, - "name": "Fire Wyrm", - "max_hp": 120, - "max_dmg": 110, - "armor": 90, - "level": 32, - "max_exp": 260, - "max_gold": 87, - "immune": 0 - }, - { - "id": 95, - "name": "Lesser Wyvern", - "max_hp": 122, - "max_dmg": 110, - "armor": 92, - "level": 32, - "max_exp": 270, - "max_gold": 90, - "immune": 0 - }, - { - "id": 96, - "name": "Doomer", - "max_hp": 124, - "max_dmg": 112, - "armor": 92, - "level": 32, - "max_exp": 270, - "max_gold": 90, - "immune": 0 - }, - { - "id": 97, - "name": "Armor Knight", - "max_hp": 130, - "max_dmg": 115, - "armor": 95, - "level": 33, - "max_exp": 280, - "max_gold": 93, - "immune": 0 - }, - { - "id": 98, - "name": "Wyvern", - "max_hp": 134, - "max_dmg": 120, - "armor": 95, - "level": 33, - "max_exp": 290, - "max_gold": 97, - "immune": 0 - }, - { - "id": 99, - "name": "Nightmare", - "max_hp": 138, - "max_dmg": 125, - "armor": 100, - "level": 33, - "max_exp": 300, - "max_gold": 100, - "immune": 0 - }, - { - "id": 100, - "name": "Fira Elemental", - "max_hp": 140, - "max_dmg": 125, - "armor": 100, - "level": 34, - "max_exp": 310, - "max_gold": 103, - "immune": 1 - }, - { - "id": 101, - "name": "Megadoomer", - "max_hp": 140, - "max_dmg": 128, - "armor": 105, - "level": 34, - "max_exp": 320, - "max_gold": 107, - "immune": 0 - }, - { - "id": 102, - "name": "Greater Wyvern", - "max_hp": 145, - "max_dmg": 130, - "armor": 105, - "level": 34, - "max_exp": 335, - "max_gold": 112, - "immune": 0 - }, - { - "id": 103, - "name": "Advocate", - "max_hp": 148, - "max_dmg": 132, - "armor": 108, - "level": 35, - "max_exp": 350, - "max_gold": 117, - "immune": 0 - }, - { - "id": 104, - "name": "Strong Knight", - "max_hp": 150, - "max_dmg": 135, - "armor": 110, - "level": 35, - "max_exp": 365, - "max_gold": 122, - "immune": 0 - }, - { - "id": 105, - "name": "Liche", - "max_hp": 150, - "max_dmg": 135, - "armor": 110, - "level": 35, - "max_exp": 380, - "max_gold": 127, - "immune": 0 - }, - { - "id": 106, - "name": "Ultradoomer", - "max_hp": 155, - "max_dmg": 140, - "armor": 115, - "level": 36, - "max_exp": 395, - "max_gold": 132, - "immune": 0 - }, - { - "id": 107, - "name": "Fanatic", - "max_hp": 160, - "max_dmg": 140, - "armor": 115, - "level": 36, - "max_exp": 410, - "max_gold": 137, - "immune": 0 - }, - { - "id": 108, - "name": "Green Dragon", - "max_hp": 160, - "max_dmg": 140, - "armor": 115, - "level": 36, - "max_exp": 425, - "max_gold": 142, - "immune": 0 - }, - { - "id": 109, - "name": "Fiend", - "max_hp": 160, - "max_dmg": 145, - "armor": 120, - "level": 37, - "max_exp": 445, - "max_gold": 148, - "immune": 0 - }, - { - "id": 110, - "name": "Greatest Wyvern", - "max_hp": 162, - "max_dmg": 150, - "armor": 120, - "level": 37, - "max_exp": 465, - "max_gold": 155, - "immune": 0 - }, - { - "id": 111, - "name": "Lesser Devil", - "max_hp": 164, - "max_dmg": 150, - "armor": 120, - "level": 37, - "max_exp": 485, - "max_gold": 162, - "immune": 0 - }, - { - "id": 112, - "name": "Liche Master", - "max_hp": 168, - "max_dmg": 155, - "armor": 125, - "level": 38, - "max_exp": 505, - "max_gold": 168, - "immune": 0 - }, - { - "id": 113, - "name": "Zealot", - "max_hp": 168, - "max_dmg": 155, - "armor": 125, - "level": 38, - "max_exp": 530, - "max_gold": 177, - "immune": 0 - }, - { - "id": 114, - "name": "Serafiend", - "max_hp": 170, - "max_dmg": 155, - "armor": 125, - "level": 38, - "max_exp": 555, - "max_gold": 185, - "immune": 0 - }, - { - "id": 115, - "name": "Pale Knight", - "max_hp": 175, - "max_dmg": 160, - "armor": 130, - "level": 39, - "max_exp": 580, - "max_gold": 193, - "immune": 0 - }, - { - "id": 116, - "name": "Blue Dragon", - "max_hp": 180, - "max_dmg": 160, - "armor": 130, - "level": 39, - "max_exp": 605, - "max_gold": 202, - "immune": 0 - }, - { - "id": 117, - "name": "Obsessive", - "max_hp": 180, - "max_dmg": 160, - "armor": 135, - "level": 40, - "max_exp": 630, - "max_gold": 210, - "immune": 0 - }, - { - "id": 118, - "name": "Devil", - "max_hp": 184, - "max_dmg": 164, - "armor": 135, - "level": 40, - "max_exp": 666, - "max_gold": 222, - "immune": 0 - }, - { - "id": 119, - "name": "Liche Prince", - "max_hp": 190, - "max_dmg": 168, - "armor": 138, - "level": 40, - "max_exp": 660, - "max_gold": 220, - "immune": 0 - }, - { - "id": 120, - "name": "Cherufiend", - "max_hp": 195, - "max_dmg": 170, - "armor": 140, - "level": 41, - "max_exp": 690, - "max_gold": 230, - "immune": 0 - }, - { - "id": 121, - "name": "Red Dragon", - "max_hp": 200, - "max_dmg": 180, - "armor": 145, - "level": 41, - "max_exp": 720, - "max_gold": 240, - "immune": 0 - }, - { - "id": 122, - "name": "Greater Devil", - "max_hp": 200, - "max_dmg": 180, - "armor": 145, - "level": 41, - "max_exp": 750, - "max_gold": 250, - "immune": 0 - }, - { - "id": 123, - "name": "Renegade", - "max_hp": 205, - "max_dmg": 185, - "armor": 150, - "level": 42, - "max_exp": 780, - "max_gold": 260, - "immune": 0 - }, - { - "id": 124, - "name": "Archfiend", - "max_hp": 210, - "max_dmg": 190, - "armor": 150, - "level": 42, - "max_exp": 810, - "max_gold": 270, - "immune": 0 - }, - { - "id": 125, - "name": "Liche Lord", - "max_hp": 210, - "max_dmg": 190, - "armor": 155, - "level": 42, - "max_exp": 850, - "max_gold": 283, - "immune": 0 - }, - { - "id": 126, - "name": "Greatest Devil", - "max_hp": 215, - "max_dmg": 195, - "armor": 160, - "level": 43, - "max_exp": 890, - "max_gold": 297, - "immune": 0 - }, - { - "id": 127, - "name": "Dark Knight", - "max_hp": 220, - "max_dmg": 200, - "armor": 160, - "level": 43, - "max_exp": 930, - "max_gold": 310, - "immune": 0 - }, - { - "id": 128, - "name": "Giant", - "max_hp": 220, - "max_dmg": 200, - "armor": 165, - "level": 43, - "max_exp": 970, - "max_gold": 323, - "immune": 0 - }, - { - "id": 129, - "name": "Shadow Dragon", - "max_hp": 225, - "max_dmg": 200, - "armor": 170, - "level": 44, - "max_exp": 1010, - "max_gold": 337, - "immune": 0 - }, - { - "id": 130, - "name": "Liche King", - "max_hp": 225, - "max_dmg": 205, - "armor": 170, - "level": 44, - "max_exp": 1050, - "max_gold": 350, - "immune": 0 - }, - { - "id": 131, - "name": "Incubus", - "max_hp": 230, - "max_dmg": 205, - "armor": 175, - "level": 44, - "max_exp": 1100, - "max_gold": 367, - "immune": 1 - }, - { - "id": 132, - "name": "Traitor", - "max_hp": 230, - "max_dmg": 205, - "armor": 175, - "level": 45, - "max_exp": 1150, - "max_gold": 383, - "immune": 0 - }, - { - "id": 133, - "name": "Demon", - "max_hp": 240, - "max_dmg": 210, - "armor": 180, - "level": 45, - "max_exp": 1200, - "max_gold": 400, - "immune": 0 - }, - { - "id": 134, - "name": "Dark Dragon", - "max_hp": 245, - "max_dmg": 215, - "armor": 180, - "level": 45, - "max_exp": 1250, - "max_gold": 417, - "immune": 1 - }, - { - "id": 135, - "name": "Insurgent", - "max_hp": 250, - "max_dmg": 220, - "armor": 190, - "level": 46, - "max_exp": 1300, - "max_gold": 433, - "immune": 0 - }, - { - "id": 136, - "name": "Leviathan", - "max_hp": 255, - "max_dmg": 225, - "armor": 190, - "level": 46, - "max_exp": 1350, - "max_gold": 450, - "immune": 0 - }, - { - "id": 137, - "name": "Grey Daemon", - "max_hp": 260, - "max_dmg": 230, - "armor": 190, - "level": 46, - "max_exp": 1400, - "max_gold": 467, - "immune": 0 - }, - { - "id": 138, - "name": "Succubus", - "max_hp": 265, - "max_dmg": 240, - "armor": 200, - "level": 47, - "max_exp": 1460, - "max_gold": 487, - "immune": 1 - }, - { - "id": 139, - "name": "Demon Prince", - "max_hp": 270, - "max_dmg": 240, - "armor": 200, - "level": 47, - "max_exp": 1520, - "max_gold": 507, - "immune": 0 - }, - { - "id": 140, - "name": "Black Dragon", - "max_hp": 275, - "max_dmg": 250, - "armor": 205, - "level": 47, - "max_exp": 1580, - "max_gold": 527, - "immune": 1 - }, - { - "id": 141, - "name": "Nihilist", - "max_hp": 280, - "max_dmg": 250, - "armor": 205, - "level": 47, - "max_exp": 1640, - "max_gold": 547, - "immune": 0 - }, - { - "id": 142, - "name": "Behemoth", - "max_hp": 285, - "max_dmg": 260, - "armor": 210, - "level": 48, - "max_exp": 1700, - "max_gold": 567, - "immune": 0 - }, - { - "id": 143, - "name": "Demagogue", - "max_hp": 290, - "max_dmg": 260, - "armor": 210, - "level": 48, - "max_exp": 1760, - "max_gold": 587, - "immune": 0 - }, - { - "id": 144, - "name": "Demon Lord", - "max_hp": 300, - "max_dmg": 270, - "armor": 220, - "level": 48, - "max_exp": 1820, - "max_gold": 607, - "immune": 0 - }, - { - "id": 145, - "name": "Red Daemon", - "max_hp": 310, - "max_dmg": 280, - "armor": 230, - "level": 48, - "max_exp": 1880, - "max_gold": 627, - "immune": 0 - }, - { - "id": 146, - "name": "Colossus", - "max_hp": 320, - "max_dmg": 300, - "armor": 240, - "level": 49, - "max_exp": 1940, - "max_gold": 647, - "immune": 0 - }, - { - "id": 147, - "name": "Demon King", - "max_hp": 330, - "max_dmg": 300, - "armor": 250, - "level": 49, - "max_exp": 2000, - "max_gold": 667, - "immune": 0 - }, - { - "id": 148, - "name": "Dark Daemon", - "max_hp": 340, - "max_dmg": 320, - "armor": 260, - "level": 49, - "max_exp": 2200, - "max_gold": 733, - "immune": 1 - }, - { - "id": 149, - "name": "Titan", - "max_hp": 360, - "max_dmg": 340, - "armor": 270, - "level": 50, - "max_exp": 2400, - "max_gold": 800, - "immune": 0 - }, - { - "id": 150, - "name": "Black Daemon", - "max_hp": 400, - "max_dmg": 400, - "armor": 280, - "level": 50, - "max_exp": 3000, - "max_gold": 1000, - "immune": 1 - }, - { - "id": 151, - "name": "Lucifuge", - "max_hp": 600, - "max_dmg": 600, - "armor": 400, - "level": 50, - "max_exp": 10000, - "max_gold": 10000, - "immune": 2 - } -] \ No newline at end of file diff --git a/data/news.json b/data/news.json deleted file mode 100644 index 0637a08..0000000 --- a/data/news.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/data/spells.json b/data/spells.json deleted file mode 100644 index 5065e5e..0000000 --- a/data/spells.json +++ /dev/null @@ -1,135 +0,0 @@ -[ - { - "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 - } -] \ No newline at end of file diff --git a/data/towns.json b/data/towns.json deleted file mode 100644 index c613127..0000000 --- a/data/towns.json +++ /dev/null @@ -1,82 +0,0 @@ -[ - { - "id": 1, - "name": "Midworld", - "x": 0, - "y": 0, - "inn_cost": 1, - "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" - } -] \ No newline at end of file diff --git a/database.sql b/database.sql index 9feae27..c19acb0 100644 --- a/database.sql +++ b/database.sql @@ -12,7 +12,7 @@ CREATE TABLE drops ( `name` TEXT NOT NULL, `level` INTEGER NOT NULL DEFAULT 0, `type` INTEGER NOT NULL DEFAULT 0, - `att` TEXT NOT NULL DEFAULT '', + `att` TEXT NOT NULL DEFAULT '' ); INSERT INTO drops VALUES (1, 'Life Pebble', 1, 1, 'maxhp,10'), @@ -117,7 +117,7 @@ CREATE TABLE classes ( 'hp_rate' INTEGER NOT NULL DEFAULT 2, 'mp_rate' INTEGER NOT NULL DEFAULT 2, 'str_rate' INTEGER NOT NULL DEFAULT 2, - 'dex_rate' INTEGER NOT NULL DEFAULT 2, + 'dex_rate' INTEGER NOT NULL DEFAULT 2 ); INSERT INTO classes VALUES (1, 'Adventurer', '', 3, 15, 10, 4, 4, 2, 2, 2, 2), @@ -347,4 +347,83 @@ INSERT INTO towns VALUES (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'); \ No newline at end of file +(8, 'Endworld', -250, -250, 125, 9000, 160, '16,27,33'); + +DROP TABLE IF EXISTS 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 1, + `currently` TEXT NOT NULL DEFAULT 'In Town', + `fight_id` INTEGER NOT NULL DEFAULT 0, + `hp` INTEGER NOT NULL DEFAULT 10, + `mp` INTEGER NOT NULL DEFAULT 10, + `tp` INTEGER NOT NULL DEFAULT 10, + `max_hp` INTEGER NOT NULL DEFAULT 10, + `max_mp` INTEGER NOT NULL DEFAULT 10, + `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 0, + `dexterity` INTEGER NOT NULL DEFAULT 0, + `attack` INTEGER NOT NULL DEFAULT 0, + `defense` INTEGER NOT NULL DEFAULT 0, + `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 '', + `spells` TEXT NOT NULL DEFAULT '', + `towns` TEXT NOT NULL DEFAULT '' +); + +DROP TABLE IF EXISTS fights; +CREATE TABLE fights ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `user_id` INTEGER NOT NULL, + `monster_id` INTEGER NOT NULL, + `monster_hp` INTEGER NOT NULL DEFAULT 0, + `monster_max_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, + `first_strike` INTEGER NOT NULL DEFAULT 0, + `turn` INTEGER NOT NULL DEFAULT 0, + `ran_away` INTEGER NOT NULL DEFAULT 0, + `victory` INTEGER NOT NULL DEFAULT 0, + `won` INTEGER NOT NULL DEFAULT 0, + `reward_gold` INTEGER NOT NULL DEFAULT 0, + `reward_exp` INTEGER NOT NULL DEFAULT 0, + `created` INTEGER NOT NULL DEFAULT (unixepoch()), + `updated` INTEGER NOT NULL DEFAULT (unixepoch()) +); + +DROP TABLE IF EXISTS fight_logs; +CREATE TABLE fight_logs ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `fight_id` INTEGER NOT NULL, + `type` INTEGER NOT NULL DEFAULT 7, + `data` INTEGER NOT NULL DEFAULT 0, + `name` TEXT NOT NULL DEFAULT '', + `created` INTEGER NOT NULL DEFAULT (unixepoch()) +); diff --git a/go.mod b/go.mod index 95113cd..cfa7860 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module dk go 1.25.0 require ( - git.sharkk.net/Sharkk/Nigiri v1.0.0 git.sharkk.net/Sharkk/Sushi v1.1.1 github.com/valyala/fasthttp v1.65.0 + zombiezen.com/go/sqlite v1.4.2 ) require ( @@ -24,5 +24,4 @@ require ( modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.37.1 // indirect - zombiezen.com/go/sqlite v1.4.2 // indirect ) diff --git a/go.sum b/go.sum index 256a47a..445de1f 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ -git.sharkk.net/Sharkk/Nigiri v1.0.0 h1:N0MvWOoX54iXjR8D1LqGIFrtMAPdaoj/32n13Ou/p90= -git.sharkk.net/Sharkk/Nigiri v1.0.0/go.mod h1:HWpMtXaodPXE7dZXQ6tbZNL0DRV9PT65D0DOV0NAwsM= git.sharkk.net/Sharkk/Sushi v1.1.1 h1:ynU16l6vAhY/JUwHlI4zMQiPuL9lcs88W/mAGZsL4Rw= git.sharkk.net/Sharkk/Sushi v1.1.1/go.mod h1:S84ACGkuZ+BKzBO4lb5WQnm5aw9+l7VSO2T1bjzxL3o= 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= @@ -26,16 +26,40 @@ 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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +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= diff --git a/internal/actions/fight.go b/internal/actions/fight.go index 83354e6..5de0f68 100644 --- a/internal/actions/fight.go +++ b/internal/actions/fight.go @@ -1,6 +1,9 @@ package actions import ( + "dk/internal/database" + "dk/internal/helpers/exp" + "dk/internal/models/fightlogs" "dk/internal/models/fights" "dk/internal/models/monsters" "dk/internal/models/spells" @@ -8,210 +11,273 @@ import ( "dk/internal/models/users" "math" "math/rand" - "strconv" ) -func HandleAttack(fight *fights.Fight, user *users.User) { - // Load monster data to get armor +type FightResult struct { + FightUpdates map[string]any + UserUpdates map[string]any + LogAction func() error + Ended bool + Victory bool + Won bool +} + +func HandleAttack(fight *fights.Fight, user *users.User) *FightResult { monster, err := monsters.Find(fight.MonsterID) if err != nil { - fight.AddAction("Monster not found!") - return + return &FightResult{ + LogAction: func() error { return fightlogs.AddAction(fight.ID, "Monster not found!") }, + } } - // Player attack damage calculation with sqrt scaling + // Calculate damage attackPower := float64(user.Attack) minAttack := attackPower * 0.75 maxAttack := attackPower rawAttack := math.Ceil(rand.Float64()*(maxAttack-minAttack) + minAttack) - - // Progressive scaling using square root for smooth progression tohit := rawAttack / (1.2 + math.Sqrt(attackPower)*0.05) - // Critical hit chance based on strength + // Critical hit criticalRoll := rand.Intn(150) + 1 if float64(criticalRoll) <= math.Sqrt(float64(user.Strength)) { - tohit *= 2 // Critical hit + tohit *= 2 } - // Monster defense calculation with more aggressive scaling + // Monster defense armor := float64(monster.Armor) minBlock := armor * 0.75 maxBlock := armor rawBlock := math.Ceil(rand.Float64()*(maxBlock-minBlock) + minBlock) - - // Armor uses higher divisor to balance against player attack toblock := rawBlock / (1.8 + math.Sqrt(armor)*0.08) - // Calculate final damage damage := tohit - toblock if damage < 1 { - damage = 1 // Minimum damage + damage = 1 } - // Apply uber damage bonus if fight.UberDamage > 0 { bonus := math.Ceil(damage * float64(fight.UberDamage) / 100) damage += bonus } finalDamage := int(damage) - - // Apply damage and add action - fight.DamageMonster(finalDamage) - fight.AddActionAttackHit(finalDamage) - - // Check if monster is defeated - if fight.MonsterHP <= 0 { - fight.AddActionMonsterDeath(monster.Name) - rewardGold, rewardExp := calculateRewards(monster, user) - fight.WinFight(rewardGold, rewardExp) - HandleFightWin(fight, user) + newMonsterHP := fight.MonsterHP - finalDamage + if newMonsterHP < 0 { + newMonsterHP = 0 } + + result := &FightResult{ + FightUpdates: map[string]any{"monster_hp": newMonsterHP}, + LogAction: func() error { return fightlogs.AddAttackHit(fight.ID, finalDamage) }, + } + + // Check if monster defeated + if newMonsterHP <= 0 { + rewardGold, rewardExp := calculateRewards(monster, user) + + result.FightUpdates["victory"] = true + result.FightUpdates["won"] = true + result.FightUpdates["reward_gold"] = rewardGold + result.FightUpdates["reward_exp"] = rewardExp + + result.UserUpdates = map[string]any{ + "fight_id": 0, + "currently": "Exploring", + "gold": user.Gold + rewardGold, + "exp": user.Exp + rewardExp, + } + + // Handle level up + newLevel, newStats := calculateLevelUp(user.Level, user.Exp+rewardExp, user.Strength, user.Dexterity) + if newLevel > user.Level { + result.UserUpdates["level"] = newLevel + result.UserUpdates["strength"] = newStats.Strength + result.UserUpdates["dexterity"] = newStats.Dexterity + } + + result.LogAction = func() error { + if err := fightlogs.AddAttackHit(fight.ID, finalDamage); err != nil { + return err + } + return fightlogs.AddMonsterDeath(fight.ID, monster.Name) + } + result.Ended = true + result.Victory = true + result.Won = true + } + + return result } -func HandleSpell(fight *fights.Fight, user *users.User, spellID int) { +func HandleSpell(fight *fights.Fight, user *users.User, spellID int) *FightResult { spell, err := spells.Find(spellID) if err != nil { - fight.AddAction("Spell not found!") - return + return &FightResult{ + LogAction: func() error { return fightlogs.AddAction(fight.ID, "Spell not found!") }, + } } - // Check if user has enough MP if user.MP < spell.MP { - fight.AddAction("Not enough MP to cast " + spell.Name + "!") - return + return &FightResult{ + LogAction: func() error { return fightlogs.AddAction(fight.ID, "Not enough MP to cast "+spell.Name+"!") }, + } } - // Check if user knows this spell if !user.HasSpell(spellID) { - fight.AddAction("You don't know that spell!") - return + return &FightResult{ + LogAction: func() error { return fightlogs.AddAction(fight.ID, "You don't know that spell!") }, + } } - // Deduct MP - user.MP -= spell.MP + result := &FightResult{ + UserUpdates: map[string]any{"mp": user.MP - spell.MP}, + } switch spell.Type { case spells.TypeHealing: - // Heal user - healAmount := spell.Attribute - user.HP += healAmount - if user.HP > user.MaxHP { - user.HP = user.MaxHP + newHP := user.HP + spell.Attribute + if newHP > user.MaxHP { + newHP = user.MaxHP } - fight.AddAction("You cast " + spell.Name + " and healed " + strconv.Itoa(healAmount) + " HP!") + result.UserUpdates["hp"] = newHP + result.LogAction = func() error { return fightlogs.AddSpellHeal(fight.ID, spell.Name, spell.Attribute) } case spells.TypeHurt: - // Damage monster - damage := spell.Attribute - fight.DamageMonster(damage) - fight.AddAction("You cast " + spell.Name + " and dealt " + strconv.Itoa(damage) + " damage!") + newMonsterHP := fight.MonsterHP - spell.Attribute + if newMonsterHP < 0 { + newMonsterHP = 0 + } + result.FightUpdates = map[string]any{"monster_hp": newMonsterHP} + result.LogAction = func() error { return fightlogs.AddSpellHurt(fight.ID, spell.Name, spell.Attribute) } - // Check if monster is defeated - if fight.MonsterHP <= 0 { - fight.WinFight(10, 5) // Basic rewards + if newMonsterHP <= 0 { + result.FightUpdates["victory"] = true + result.FightUpdates["won"] = true + result.FightUpdates["reward_gold"] = 10 + result.FightUpdates["reward_exp"] = 5 + result.UserUpdates["fight_id"] = 0 + result.UserUpdates["currently"] = "Exploring" + result.Ended = true + result.Victory = true + result.Won = true } default: - fight.AddAction("You cast " + spell.Name + " but nothing happened!") + result.LogAction = func() error { return fightlogs.AddAction(fight.ID, "You cast "+spell.Name+" but nothing happened!") } } + + return result } -func HandleRun(fight *fights.Fight, user *users.User) { - // 20% chance to successfully run away +func HandleRun(fight *fights.Fight, user *users.User) *FightResult { + result := &FightResult{} + if rand.Float32() < 0.2 { - fight.RunAway() - user.FightID = 0 - fight.AddAction("You successfully ran away!") + result.FightUpdates = map[string]any{"ran_away": true} + result.UserUpdates = map[string]any{ + "fight_id": 0, + "currently": "Exploring", + } + result.LogAction = func() error { return fightlogs.AddRunSuccess(fight.ID) } + result.Ended = true } else { - fight.AddAction("You failed to run away!") + result.LogAction = func() error { return fightlogs.AddRunFail(fight.ID) } } + + return result } -func HandleMonsterAttack(fight *fights.Fight, user *users.User) { - // Load monster data +func HandleMonsterAttack(fight *fights.Fight, user *users.User) *FightResult { monster, err := monsters.Find(fight.MonsterID) if err != nil { - return + return &FightResult{} } - // Monster attack damage calculation + // Calculate damage attackPower := float64(monster.MaxDmg) minAttack := attackPower * 0.75 maxAttack := attackPower tohit := math.Ceil(rand.Float64()*(maxAttack-minAttack)+minAttack) / 3 - // User defense calculation defense := float64(user.Defense) minBlock := defense * 0.75 maxBlock := defense toblock := math.Ceil(rand.Float64()*(maxBlock-minBlock)+minBlock) / 3 - // Calculate final damage damage := tohit - toblock if damage < 1 { - damage = 1 // Minimum damage + damage = 1 } - // Apply uber defense bonus (reduces damage taken) if fight.UberDefense > 0 { reduction := math.Ceil(damage * float64(fight.UberDefense) / 100) damage -= reduction if damage < 1 { - damage = 1 // Still minimum 1 damage + damage = 1 } } finalDamage := int(damage) - - // Apply damage to user - user.HP -= finalDamage - if user.HP < 0 { - user.HP = 0 + newHP := user.HP - finalDamage + if newHP < 0 { + newHP = 0 } - // Add monster attack action using memory-optimized format - fight.AddActionMonsterAttack(monster.Name, finalDamage) - - // Check if user is defeated - if user.HP <= 0 { - fight.LoseFight() - HandleFightLoss(fight, user) + result := &FightResult{ + UserUpdates: map[string]any{"hp": newHP}, + LogAction: func() error { return fightlogs.AddMonsterAttack(fight.ID, monster.Name, finalDamage) }, } + + if newHP <= 0 { + closestTown := findClosestTown(user.X, user.Y) + townX, townY := 0, 0 + if closestTown != nil { + townX, townY = closestTown.X, closestTown.Y + } + + result.FightUpdates = map[string]any{ + "victory": true, + "won": false, + } + result.UserUpdates = map[string]any{ + "fight_id": 0, + "currently": "In Town", + "hp": user.MaxHP / 4, + "gold": (user.Gold * 3) / 4, + "x": townX, + "y": townY, + } + result.Ended = true + result.Victory = true + result.Won = false + } + + return result } -func HandleFightWin(fight *fights.Fight, user *users.User) { - // Add rewards to user - user.GrantExp(fight.RewardExp) - user.Gold += fight.RewardGold - - // Reset fight state - user.FightID = 0 - user.Currently = "Exploring" - - fight.Save() - user.Save() +type LevelStats struct { + Strength int + Dexterity int } -func HandleFightLoss(fight *fights.Fight, user *users.User) { - // Find closest town to user's position - closestTown := findClosestTown(user.X, user.Y) - if closestTown != nil { - user.X = closestTown.X - user.Y = closestTown.Y +func calculateLevelUp(currentLevel, newExp, currentStr, currentDex int) (int, LevelStats) { + level := currentLevel + str := currentStr + dex := currentDex + nexp := newExp + + for { + expNeeded := exp.Calc(level + 1) + if nexp < expNeeded { + break + } + level++ + str++ + dex++ + nexp -= expNeeded } - // Apply death penalties - user.HP = user.MaxHP / 4 // 25% of max health - user.Gold = (user.Gold * 3) / 4 // 75% of gold - - // Reset fight state - user.FightID = 0 - user.Currently = "In Town" - - fight.Save() - user.Save() + return level, LevelStats{Strength: str, Dexterity: dex} } func findClosestTown(x, y int) *towns.Town { @@ -235,7 +301,6 @@ func findClosestTown(x, y int) *towns.Town { } func calculateRewards(monster *monsters.Monster, user *users.User) (int, int) { - // Base rewards (83-100% of max) minExp := (monster.MaxExp * 5) / 6 maxExp := monster.MaxExp exp := rand.Intn(maxExp-minExp+1) + minExp @@ -244,7 +309,6 @@ func calculateRewards(monster *monsters.Monster, user *users.User) (int, int) { maxGold := monster.MaxGold gold := rand.Intn(maxGold-minGold+1) + minGold - // Apply bonus multipliers expBonus := (user.ExpBonus * exp) / 100 exp += expBonus @@ -253,3 +317,32 @@ func calculateRewards(monster *monsters.Monster, user *users.User) (int, int) { return gold, exp } + +func ExecuteFightAction(fightID int, result *FightResult) error { + return database.Transaction(func() error { + // Update fight + if len(result.FightUpdates) > 0 { + if err := database.Update("fights", result.FightUpdates, "id", fightID); err != nil { + return err + } + } + + // Update user + if len(result.UserUpdates) > 0 { + fight, err := fights.Find(fightID) + if err != nil { + return err + } + if err := database.Update("users", result.UserUpdates, "id", fight.UserID); err != nil { + return err + } + } + + // Add log entry + if result.LogAction != nil { + return result.LogAction() + } + + return nil + }) +} diff --git a/internal/actions/move.go b/internal/actions/move.go index ae1484d..02942ea 100644 --- a/internal/actions/move.go +++ b/internal/actions/move.go @@ -1,7 +1,7 @@ package actions import ( - "dk/internal/models/control" + "dk/internal/control" "dk/internal/models/fights" "dk/internal/models/monsters" "dk/internal/models/towns" diff --git a/internal/actions/user_item.go b/internal/actions/user_item.go index 3485ca4..635c3ab 100644 --- a/internal/actions/user_item.go +++ b/internal/actions/user_item.go @@ -5,52 +5,53 @@ import ( "dk/internal/models/users" ) -// UserEquipItem equips a given item onto a user. This overwrites any -// previously equipped item in the slot. Does not save. -func UserEquipItem(user *users.User, item *items.Item) { - slotInUse := false - if item.Type == items.TypeWeapon && user.WeaponID != 0 { - slotInUse = true - } - if item.Type == items.TypeArmor && user.ArmorID != 0 { - slotInUse = true - } - if item.Type == items.TypeShield && user.ShieldID != 0 { - slotInUse = true - } +// UserEquipItem calculates equipment updates for a user equipping an item. +// Returns map of database field updates without modifying the user struct. +func UserEquipItem(user *users.User, item *items.Item) map[string]any { + updates := make(map[string]any) - var oldItem *items.Item - if slotInUse && item.Type == items.TypeWeapon { - oldItem, _ = items.Find(user.WeaponID) - } else if slotInUse && item.Type == items.TypeArmor { - oldItem, _ = items.Find(user.ArmorID) - } else if slotInUse && item.Type == items.TypeShield { - oldItem, _ = items.Find(user.ShieldID) - } - - if oldItem != nil { - switch oldItem.Type { - case items.TypeWeapon: - user.Attack -= oldItem.Att - case items.TypeArmor: - user.Defense -= oldItem.Att - case items.TypeShield: - user.Defense -= oldItem.Att - } - } + // Calculate stat changes + newAttack := user.Attack + newDefense := user.Defense + // Remove old item stats if slot occupied switch item.Type { case items.TypeWeapon: - user.Attack += item.Att - user.WeaponID = item.ID - user.WeaponName = item.Name + if user.WeaponID != 0 { + if oldItem, err := items.Find(user.WeaponID); err == nil { + newAttack -= oldItem.Att + } + } + // Add new item + newAttack += item.Att + updates["weapon_id"] = item.ID + updates["weapon_name"] = item.Name + case items.TypeArmor: - user.Defense += item.Att - user.ArmorID = item.ID - user.ArmorName = item.Name + if user.ArmorID != 0 { + if oldItem, err := items.Find(user.ArmorID); err == nil { + newDefense -= oldItem.Att + } + } + // Add new item + newDefense += item.Att + updates["armor_id"] = item.ID + updates["armor_name"] = item.Name + case items.TypeShield: - user.Defense += item.Att - user.ShieldID = item.ID - user.ShieldName = item.Name + if user.ShieldID != 0 { + if oldItem, err := items.Find(user.ShieldID); err == nil { + newDefense -= oldItem.Att + } + } + // Add new item + newDefense += item.Att + updates["shield_id"] = item.ID + updates["shield_name"] = item.Name } + + updates["attack"] = newAttack + updates["defense"] = newDefense + + return updates } diff --git a/internal/database/wrapper.go b/internal/database/wrapper.go index 0494c44..a61f430 100644 --- a/internal/database/wrapper.go +++ b/internal/database/wrapper.go @@ -320,6 +320,28 @@ func Insert(tableName string, obj any, excludeFields ...string) (int64, error) { return DB().LastInsertRowID(), nil } +// Transaction executes multiple operations atomically +func Transaction(fn func() error) error { + conn := DB() + + // Begin transaction + if err := sqlitex.Execute(conn, "BEGIN", nil); err != nil { + return err + } + + // Execute operations + err := fn() + + if err != nil { + // Rollback on error + sqlitex.Execute(conn, "ROLLBACK", nil) + return err + } + + // Commit on success + return sqlitex.Execute(conn, "COMMIT", nil) +} + func convertPlaceholders(query string) (string, []string) { var paramTypes []string diff --git a/internal/routes/auth.go b/internal/routes/auth.go index 2555b8b..c30e1ca 100644 --- a/internal/routes/auth.go +++ b/internal/routes/auth.go @@ -61,6 +61,9 @@ func processLogin(ctx sushi.Ctx) { return } + // Update last online time when logging in + user.UpdateLastOnline() + ctx.Login(user.ID, user) // Set success message @@ -92,10 +95,10 @@ func showRegister(ctx sushi.Ctx) { // processRegister handles registration form submission func processRegister(ctx sushi.Ctx) { - username := strings.TrimSpace(string(ctx.PostArgs().Peek("username"))) - email := strings.TrimSpace(string(ctx.PostArgs().Peek("email"))) - userPassword := string(ctx.PostArgs().Peek("password")) - confirmPassword := string(ctx.PostArgs().Peek("confirm_password")) + username := strings.TrimSpace(ctx.Form("username").String()) + email := strings.TrimSpace(ctx.Form("email").String()) + userPassword := ctx.Form("password").String() + confirmPassword := ctx.Form("confirm_password").String() formData := map[string]string{ "username": username, @@ -108,18 +111,21 @@ func processRegister(ctx sushi.Ctx) { return } + // Check if username already exists if _, err := users.ByUsername(username); err == nil { setFlashAndFormData(ctx, "Username already exists", formData) ctx.Redirect("/register") return } + // Check if email already exists if _, err := users.ByEmail(email); err == nil { setFlashAndFormData(ctx, "Email already registered", formData) ctx.Redirect("/register") return } + // Create new user user := users.New() user.Username = username user.Email = email @@ -127,6 +133,13 @@ func processRegister(ctx sushi.Ctx) { user.ClassID = 1 user.Auth = 1 + // Validate before inserting + if err := user.Validate(); err != nil { + setFlashAndFormData(ctx, fmt.Sprintf("Invalid user data: %s", err.Error()), formData) + ctx.Redirect("/register") + return + } + if err := user.Insert(); err != nil { setFlashAndFormData(ctx, "Failed to create account", formData) ctx.Redirect("/register") @@ -186,22 +199,22 @@ func authenticate(usernameOrEmail, plainPassword string) (*users.User, error) { var user *users.User var err error + // Try username first user, err = users.ByUsername(usernameOrEmail) if err != nil { - fmt.Println(err.Error()) + // If username not found, try email user, err = users.ByEmail(usernameOrEmail) if err != nil { - fmt.Println(err.Error()) - return nil, err + return nil, fmt.Errorf("user not found") } } isValid, err := password.VerifyPassword(plainPassword, user.Password) if err != nil { - return nil, err + return nil, fmt.Errorf("password verification error: %w", err) } if !isValid { - return nil, fmt.Errorf("invalid username/email or password") + return nil, fmt.Errorf("invalid password") } return user, nil diff --git a/internal/routes/fight.go b/internal/routes/fight.go index 861db89..fe898a3 100644 --- a/internal/routes/fight.go +++ b/internal/routes/fight.go @@ -3,7 +3,9 @@ package routes import ( "dk/internal/actions" "dk/internal/components" + "dk/internal/database" "dk/internal/helpers" + "dk/internal/models/fightlogs" "dk/internal/models/fights" "dk/internal/models/monsters" "dk/internal/models/spells" @@ -25,7 +27,6 @@ func RegisterFightRoutes(app *sushi.App) { group.Post("/", handleFightAction) } -// requireFighting middleware ensures the user is in a fight func requireFighting() sushi.Middleware { return func(ctx sushi.Ctx, next func()) { user := ctx.GetCurrentUser() @@ -60,16 +61,23 @@ func showFight(ctx sushi.Ctx) { return } - // If turn 0, determine first strike and advance to turn 1 + // Initialize fight on first view if fight.Turn == 0 { - // 50% chance user goes first + err := database.Transaction(func() error { + return database.Update("fights", map[string]any{ + "first_strike": rand.Float32() < 0.5, + "turn": 1, + }, "id", fight.ID) + }) + if err != nil { + ctx.SendError(500, "Failed to initialize fight") + return + } fight.FirstStrike = rand.Float32() < 0.5 fight.Turn = 1 - fight.Save() } monHpPct := helpers.ClampPct(float64(fight.MonsterHP), float64(fight.MonsterMaxHP), 0, 100) - monHpColor := "" if monHpPct < 35 { monHpColor = "danger" @@ -86,15 +94,19 @@ func showFight(ctx sushi.Ctx) { } } + // Get recent fight actions + lastAction, _ := fightlogs.GetLastAction(fight.ID) + components.RenderPage(ctx, "Fighting", "fight/fight.html", map[string]any{ - "fight": fight, - "user": user, - "monster": monster, - "mon_hppct": monHpPct, - "mon_hpcol": monHpColor, - "spells": spellMap.ToSlice(), - "action": sess.GetFlashMessage("action"), - "mon_action": sess.GetFlashMessage("mon_action"), + "fight": fight, + "user": user, + "monster": monster, + "mon_hppct": monHpPct, + "mon_hpcol": monHpColor, + "spells": spellMap.ToSlice(), + "action": sess.GetFlashMessage("action"), + "mon_action": sess.GetFlashMessage("mon_action"), + "last_action": lastAction, }) } @@ -110,84 +122,85 @@ func handleFightAction(ctx sushi.Ctx) { } action := string(ctx.FormValue("action")) - var userAction string + var result *actions.FightResult switch action { case "attack": - actions.HandleAttack(fight, user) - userAction = fight.GetLastAction() + result = actions.HandleAttack(fight, user) case "spell": spellIDStr := string(ctx.FormValue("spell_id")) if spellID, err := strconv.Atoi(spellIDStr); err == nil { - actions.HandleSpell(fight, user, spellID) - userAction = fight.GetLastAction() + result = actions.HandleSpell(fight, user, spellID) + } else { + result = &actions.FightResult{ + LogAction: func() error { return fightlogs.AddAction(fight.ID, "Invalid spell!") }, + } } case "run": - actions.HandleRun(fight, user) - userAction = fight.GetLastAction() - - // If successfully ran away, redirect to explore - if fight.RanAway { - user.Currently = "Exploring" - user.Save() - sess.SetFlash("success", "You successfully escaped!") - ctx.Redirect("/explore", 302) - return - } + result = actions.HandleRun(fight, user) default: - fight.AddAction("Invalid action!") - userAction = "Invalid action!" + result = &actions.FightResult{ + LogAction: func() error { return fightlogs.AddAction(fight.ID, "Invalid action!") }, + } } - // Flash user action - sess.SetFlash("action", userAction) + // Execute the action + err = actions.ExecuteFightAction(fight.ID, result) + if err != nil { + ctx.SendError(500, "Failed to execute fight action") + return + } - // Check if fight ended due to user action - if fight.Victory { - if fight.Won { - // Player won - sess.SetFlash("success", fmt.Sprintf("Victory! You gained %d gold and %d experience!", fight.RewardGold, fight.RewardExp)) - sess.DeleteFlash("action") - sess.DeleteFlash("mon_action") + // Handle fight end states + if result.Ended { + if result.Won { + sess.SetFlash("success", fmt.Sprintf("Victory! You gained rewards!")) ctx.Redirect("/explore", 302) - } else { - // Player lost + } else if result.Victory { sess.SetFlash("error", "You have been defeated! You lost some gold and were sent to the nearest town.") - sess.DeleteFlash("action") - sess.DeleteFlash("mon_action") ctx.Redirect("/town", 302) + } else { + // Ran away + sess.SetFlash("success", "You successfully escaped!") + ctx.Redirect("/explore", 302) } return } - // Monster attacks back if fight is still active + // Monster attacks back if fight continues if fight.IsActive() && user.HP > 0 { - actions.HandleMonsterAttack(fight, user) + monsterResult := actions.HandleMonsterAttack(fight, user) - // Check if fight ended due to monster attack - if fight.Victory { - if fight.Won { - sess.SetFlash("success", fmt.Sprintf("Victory! You gained %d gold and %d experience!", fight.RewardGold, fight.RewardExp)) - sess.DeleteFlash("action") - sess.DeleteFlash("mon_action") + // Execute monster action + err = actions.ExecuteFightAction(fight.ID, monsterResult) + if err != nil { + ctx.SendError(500, "Failed to execute monster action") + return + } + + // Check if monster action ended fight + if monsterResult.Ended { + if monsterResult.Won { + sess.SetFlash("success", "Victory!") ctx.Redirect("/explore", 302) } else { - sess.SetFlash("error", "You have been defeated! You lost some gold and were sent to the nearest town.") - sess.DeleteFlash("action") - sess.DeleteFlash("mon_action") + sess.SetFlash("error", "You have been defeated!") ctx.Redirect("/town", 302) } return } - - monsterAction := fight.GetLastAction() - sess.SetFlash("mon_action", monsterAction) } - fight.IncrementTurn() - fight.Save() - user.Save() + // Increment turn + err = database.Transaction(func() error { + return database.Update("fights", map[string]any{ + "turn": fight.Turn + 1, + }, "id", fight.ID) + }) + if err != nil { + ctx.SendError(500, "Failed to increment turn") + return + } - // Redirect back to fight page ctx.Redirect("/fight", 302) } diff --git a/internal/routes/index.go b/internal/routes/index.go index c187bc7..dd21652 100644 --- a/internal/routes/index.go +++ b/internal/routes/index.go @@ -3,6 +3,7 @@ package routes import ( "dk/internal/actions" "dk/internal/components" + "dk/internal/database" "dk/internal/models/towns" "dk/internal/models/users" "slices" @@ -53,9 +54,18 @@ func Move(ctx sushi.Ctx) { return } - user.Currently = currently - user.SetPosition(newX, newY) - user.Save() + err = database.Transaction(func() error { + return database.Update("users", map[string]any{ + "currently": currently, + "x": newX, + "y": newY, + }, "id", user.ID) + }) + + if err != nil { + ctx.SendError(500, "failed to update user position") + return + } switch currently { case "In Town": @@ -101,10 +111,20 @@ func Teleport(ctx sushi.Ctx) { return } - user.TP -= town.TPCost - user.SetPosition(town.X, town.Y) - user.Currently = "In Town" - user.Save() + err = database.Transaction(func() error { + return database.Update("users", map[string]any{ + "tp": user.TP - town.TPCost, + "x": town.X, + "y": town.Y, + "currently": "In Town", + }, "id", user.ID) + }) + + if err != nil { + sess.SetFlash("error", "Failed to complete teleport.") + ctx.Redirect("/") + return + } sess.SetFlash("success", "You teleported to "+town.Name+" successfully!") ctx.Redirect("/town") diff --git a/internal/routes/town.go b/internal/routes/town.go index 405cac9..17abeee 100644 --- a/internal/routes/town.go +++ b/internal/routes/town.go @@ -3,6 +3,7 @@ package routes import ( "dk/internal/actions" "dk/internal/components" + "dk/internal/database" "dk/internal/helpers" "dk/internal/models/items" "dk/internal/models/towns" @@ -93,9 +94,20 @@ func rest(ctx sushi.Ctx) { return } - user.Gold -= town.InnCost - user.HP, user.MP, user.TP = user.MaxHP, user.MaxMP, user.MaxTP - user.Save() + err := database.Transaction(func() error { + return database.Update("users", map[string]any{ + "gold": user.Gold - town.InnCost, + "hp": user.MaxHP, + "mp": user.MaxMP, + "tp": user.MaxTP, + }, "id", user.ID) + }) + + if err != nil { + sess.SetFlash("error", "Failed to rest at inn.") + ctx.Redirect("/town/inn") + return + } components.RenderPage(ctx, town.Name+" Inn", "town/inn.html", map[string]any{ "town": town, @@ -158,9 +170,28 @@ func buyItem(ctx sushi.Ctx) { return } - user.Gold -= item.Value - actions.UserEquipItem(user, item) - user.Save() + // Get equipment updates from actions + equipUpdates := actions.UserEquipItem(user, item) + + err = database.Transaction(func() error { + // Start with gold deduction + updates := map[string]any{ + "gold": user.Gold - item.Value, + } + + // Add equipment updates + for field, value := range equipUpdates { + updates[field] = value + } + + return database.Update("users", updates, "id", user.ID) + }) + + if err != nil { + sess.SetFlash("error", "Failed to purchase item.") + ctx.Redirect("/town/shop") + return + } ctx.Redirect("/town/shop") } @@ -228,11 +259,22 @@ func buyMap(ctx sushi.Ctx) { return } - user.Gold -= mapped.MapCost townIDs := user.GetTownIDs() townIDs = append(townIDs, id) - user.SetTownIDs(townIDs) - user.Save() + newTownsString := helpers.IntsToString(townIDs) + + err = database.Transaction(func() error { + return database.Update("users", map[string]any{ + "gold": user.Gold - mapped.MapCost, + "towns": newTownsString, + }, "id", user.ID) + }) + + if err != nil { + sess.SetFlash("error", "Failed to purchase map.") + ctx.Redirect("/town/maps") + return + } ctx.Redirect("/town/maps") } diff --git a/main.go b/main.go index 36248de..be53797 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "path/filepath" "syscall" + "dk/internal/database" "dk/internal/models/users" "dk/internal/routes" "dk/internal/template" @@ -55,6 +56,12 @@ func start(port string) error { return fmt.Errorf("failed to get current working directory: %w", err) } + err = database.Init(filepath.Join(cwd, "data/dk.db")) + if err != nil { + log.Fatal("Failed to initialize database:", err) + } + defer database.DB().Close() + template.InitializeCache(cwd) app := sushi.New()