Compare commits

...

2 Commits

Author SHA1 Message Date
3df8f29a4c start new database creation script 2025-08-21 23:05:10 -05:00
09574294a4 finish model migration back to SQLite 2025-08-21 22:35:41 -05:00
13 changed files with 1000 additions and 991 deletions

350
database.sql Normal file
View File

@ -0,0 +1,350 @@
DROP TABLE IF EXISTS babble;
CREATE TABLE babble (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`posted` INTEGER NOT NULL DEFAULT (unixepoch()),
`author` INTEGER NOT NULL,
`babble` TEXT NOT NULL
);
DROP TABLE IF EXISTS drops;
CREATE TABLE drops (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL,
`level` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL DEFAULT 0,
`att` TEXT NOT NULL DEFAULT '',
);
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');
DROP TABLE IF EXISTS 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 DEFAULT '',
`content` TEXT NOT NULL
);
DROP TABLE IF EXISTS items;
CREATE TABLE items (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`type` INTEGER NOT NULL DEFAULT 1,
`name` TEXT NOT NULL,
`value` INTEGER NOT NULL DEFAULT 0,
`att` INTEGER NOT NULL DEFAULT 0,
`special` TEXT NOT NULL DEFAULT ''
);
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');
DROP TABLE IF EXISTS classes;
CREATE TABLE classes (
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
'name' TEXT NOT NULL,
'lore' TEXT NOT NULL,
'exp_rate' INTEGER NOT NULL DEFAULT 3,
'base_hp' INTEGER NOT NULL DEFAULT 15,
'base_mp' INTEGER NOT NULL DEFAULT 10,
'base_str' INTEGER NOT NULL DEFAULT 1,
'base_dex' INTEGER NOT NULL DEFAULT 1,
'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,
);
INSERT INTO classes VALUES
(1, 'Adventurer', '', 3, 15, 10, 4, 4, 2, 2, 2, 2),
(2, 'Mage', '', 1, 10, 15, 1, 7, 1, 3, 1, 2),
(3, 'Warrior', '', 2, 20, 5, 7, 1, 3, 1, 3, 1),
(4, 'Paladin', '', 5, 15, 15, 5, 5, 2, 2, 2, 2);
DROP TABLE IF EXISTS monsters;
CREATE TABLE monsters (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL,
`max_hp` INTEGER NOT NULL DEFAULT 1,
`max_dmg` INTEGER NOT NULL DEFAULT 1,
`armor` INTEGER NOT NULL DEFAULT 0,
`level` INTEGER NOT NULL DEFAULT 0,
`max_exp` INTEGER NOT NULL DEFAULT 1,
`max_gold` INTEGER NOT NULL DEFAULT 1,
`immune` INTEGER NOT NULL DEFAULT 0
);
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);
DROP TABLE IF EXISTS news;
CREATE TABLE news (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`author` INTEGER NOT NULL DEFAULT 0,
`posted` INTEGER NOT NULL DEFAULT (unixepoch()),
`content` TEXT NOT NULL
);
INSERT INTO news (content) VALUES ('This is the first news post. Please use the admin control panel to add another one and make this one go away.');
DROP TABLE IF EXISTS 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
);
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);
DROP TABLE IF EXISTS 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
);
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');

View File

@ -1,16 +1,16 @@
package control package control
import ( import (
"encoding/json"
"fmt" "fmt"
"os"
"sync" "sync"
nigiri "git.sharkk.net/Sharkk/Nigiri"
) )
var ( var (
store *nigiri.BaseStore[Control] global *Control
global *Control mu sync.RWMutex
mu sync.RWMutex filename string
) )
// Control represents the game control settings // Control represents the game control settings
@ -24,39 +24,72 @@ type Control struct {
Class3Name string `json:"class_3_name"` Class3Name string `json:"class_3_name"`
} }
// Init sets up the Nigiri store for control settings // Init loads control settings from the specified JSON file
func Init(collection *nigiri.Collection) { func Init(jsonFile string) error {
store = nigiri.NewBaseStore[Control]() mu.Lock()
defer mu.Unlock()
// Load or create the singleton control instance filename = jsonFile
all := store.GetAll()
if len(all) == 0 { // Try to load from file
// Create default control settings if data, err := os.ReadFile(filename); err == nil {
global = New() var ctrl Control
global.ID = 1 if err := json.Unmarshal(data, &ctrl); err != nil {
store.Add(1, global) return fmt.Errorf("failed to parse JSON: %w", err)
} else {
// Use the first (and only) control entry
for _, ctrl := range all {
global = ctrl
break
} }
// Apply defaults for any missing fields // Apply defaults for any missing fields
defaults := New() defaults := New()
if global.WorldSize == 0 { if ctrl.WorldSize == 0 {
global.WorldSize = defaults.WorldSize ctrl.WorldSize = defaults.WorldSize
} }
if global.Class1Name == "" { if ctrl.Class1Name == "" {
global.Class1Name = defaults.Class1Name ctrl.Class1Name = defaults.Class1Name
} }
if global.Class2Name == "" { if ctrl.Class2Name == "" {
global.Class2Name = defaults.Class2Name ctrl.Class2Name = defaults.Class2Name
} }
if global.Class3Name == "" { if ctrl.Class3Name == "" {
global.Class3Name = defaults.Class3Name ctrl.Class3Name = defaults.Class3Name
}
ctrl.ID = 1 // Ensure singleton ID
global = &ctrl
} else {
// Create default control settings if file doesn't exist
global = New()
if err := save(); err != nil {
return fmt.Errorf("failed to create default config file: %w", err)
} }
store.Update(global.ID, global)
} }
return global.Validate()
}
// save writes the current control settings to the JSON file (internal use)
func save() error {
if filename == "" {
return fmt.Errorf("no filename set")
}
data, err := json.MarshalIndent(global, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JSON: %w", err)
}
return os.WriteFile(filename, data, 0644)
}
// Save writes the current control settings to the JSON file (public)
func Save() error {
mu.RLock()
defer mu.RUnlock()
if global == nil {
return fmt.Errorf("control not initialized")
}
return save()
} }
// New creates a new Control with sensible defaults // New creates a new Control with sensible defaults
@ -77,12 +110,12 @@ func Get() *Control {
mu.RLock() mu.RLock()
defer mu.RUnlock() defer mu.RUnlock()
if global == nil { if global == nil {
panic("control not initialized - call Initialize first") panic("control not initialized - call Init first")
} }
return global return global
} }
// Set updates the global control instance (thread-safe) // Set updates the global control instance and saves to file (thread-safe)
func Set(control *Control) error { func Set(control *Control) error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@ -92,15 +125,11 @@ func Set(control *Control) error {
return err return err
} }
if err := store.Update(1, control); err != nil {
return err
}
global = control global = control
return nil return save()
} }
// Update updates specific fields of the control settings // Update updates specific fields of the control settings and saves to file
func Update(updater func(*Control)) error { func Update(updater func(*Control)) error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@ -113,7 +142,20 @@ func Update(updater func(*Control)) error {
return err return err
} }
if err := store.Update(1, &updated); err != nil { global = &updated
return save()
}
// UpdateNoSave updates specific fields without saving (useful for batch updates)
func UpdateNoSave(updater func(*Control)) error {
mu.Lock()
defer mu.Unlock()
// Create a copy to work with
updated := *global
updater(&updated)
if err := updated.Validate(); err != nil {
return err return err
} }
@ -234,14 +276,3 @@ func (c *Control) GetWorldBounds() (minX, minY, maxX, maxY int) {
radius := c.GetWorldRadius() radius := c.GetWorldRadius()
return -radius, -radius, radius, radius return -radius, -radius, radius, radius
} }
// Legacy compatibility functions (will be removed later)
func Load(filename string) error {
// No longer needed - Nigiri handles this
return nil
}
func Save() error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -2,19 +2,27 @@ package babble
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"time" "time"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Babble represents a global chat message in the game // Babble represents a global chat message in the game
type Babble struct { type Babble struct {
ID int `json:"id"` ID int
Posted int64 `json:"posted"` Posted int64
Author string `json:"author" db:"index"` Author int
Babble string `json:"babble"` Babble string
}
// New creates a new Babble with sensible defaults
func New() *Babble {
return &Babble{
Posted: time.Now().Unix(),
Author: 0,
Babble: "",
}
} }
// Validate checks if babble has valid values // Validate checks if babble has valid values
@ -22,8 +30,8 @@ func (b *Babble) Validate() error {
if b.Posted <= 0 { if b.Posted <= 0 {
return fmt.Errorf("babble Posted timestamp must be positive") return fmt.Errorf("babble Posted timestamp must be positive")
} }
if strings.TrimSpace(b.Author) == "" { if b.Author <= 0 {
return fmt.Errorf("babble Author cannot be empty") return fmt.Errorf("babble Author must be a valid user ID")
} }
if strings.TrimSpace(b.Babble) == "" { if strings.TrimSpace(b.Babble) == "" {
return fmt.Errorf("babble message cannot be empty") return fmt.Errorf("babble message cannot be empty")
@ -31,139 +39,71 @@ func (b *Babble) Validate() error {
return nil return nil
} }
// Global store with singleton pattern
var store *nigiri.BaseStore[Babble]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Babble]()
// Register custom indices
store.RegisterIndex("byAuthor", nigiri.BuildStringGroupIndex(func(b *Babble) string {
return strings.ToLower(b.Author)
}))
store.RegisterIndex("allByPosted", nigiri.BuildSortedListIndex(func(a, b *Babble) bool {
if a.Posted != b.Posted {
return a.Posted > b.Posted // DESC
}
return a.ID > b.ID // DESC
}))
store.RebuildIndices()
}
// GetStore returns the babble store
func GetStore() *nigiri.BaseStore[Babble] {
if store == nil {
panic("babble store not initialized - call Initialize first")
}
return store
}
// Creates a new Babble with sensible defaults
func New() *Babble {
return &Babble{
Posted: time.Now().Unix(),
Author: "",
Babble: "",
}
}
// CRUD operations
func (b *Babble) Save() error {
if b.ID == 0 {
id, err := store.Create(b)
if err != nil {
return err
}
b.ID = id
return nil
}
return store.Update(b.ID, b)
}
func (b *Babble) Delete() error { func (b *Babble) Delete() error {
store.Remove(b.ID) return database.Exec("DELETE FROM babble WHERE id = %d", b.ID)
return nil
} }
// Insert with ID assignment
func (b *Babble) Insert() error { func (b *Babble) Insert() error {
id, err := store.Create(b) id, err := database.Insert("babble", b, "ID")
if err != nil { if err != nil {
return err return err
} }
b.ID = id b.ID = int(id)
return nil return nil
} }
// Query functions
func Find(id int) (*Babble, error) { func Find(id int) (*Babble, error) {
babble, exists := store.Find(id) var babble Babble
if !exists { err := database.Get(&babble, "SELECT * FROM babble WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("babble with ID %d not found", id) return nil, fmt.Errorf("babble with ID %d not found", id)
} }
return babble, nil return &babble, nil
} }
func All() ([]*Babble, error) { func All() ([]*Babble, error) {
return store.AllSorted("allByPosted"), nil var babbles []*Babble
err := database.Select(&babbles, "SELECT * FROM babble ORDER BY posted DESC, id DESC")
return babbles, err
} }
func ByAuthor(author string) ([]*Babble, error) { func ByAuthor(authorID int) ([]*Babble, error) {
messages := store.GroupByIndex("byAuthor", strings.ToLower(author)) var babbles []*Babble
err := database.Select(&babbles, "SELECT * FROM babble WHERE author = %d ORDER BY posted DESC, id DESC", authorID)
// Sort by posted DESC, then ID DESC return babbles, err
sort.Slice(messages, func(i, j int) bool {
if messages[i].Posted != messages[j].Posted {
return messages[i].Posted > messages[j].Posted // DESC
}
return messages[i].ID > messages[j].ID // DESC
})
return messages, nil
} }
func Recent(limit int) ([]*Babble, error) { func Recent(limit int) ([]*Babble, error) {
all := store.AllSorted("allByPosted") var babbles []*Babble
if limit > len(all) { err := database.Select(&babbles, "SELECT * FROM babble ORDER BY posted DESC, id DESC LIMIT %d", limit)
limit = len(all) return babbles, err
}
return all[:limit], nil
} }
func Since(since int64) ([]*Babble, error) { func Since(since int64) ([]*Babble, error) {
return store.FilterByIndex("allByPosted", func(b *Babble) bool { var babbles []*Babble
return b.Posted >= since err := database.Select(&babbles, "SELECT * FROM babble WHERE posted >= %d ORDER BY posted DESC, id DESC", since)
}), nil return babbles, err
} }
func Between(start, end int64) ([]*Babble, error) { func Between(start, end int64) ([]*Babble, error) {
return store.FilterByIndex("allByPosted", func(b *Babble) bool { var babbles []*Babble
return b.Posted >= start && b.Posted <= end err := database.Select(&babbles, "SELECT * FROM babble WHERE posted >= %d AND posted <= %d ORDER BY posted DESC, id DESC", start, end)
}), nil return babbles, err
} }
func Search(term string) ([]*Babble, error) { func Search(term string) ([]*Babble, error) {
lowerTerm := strings.ToLower(term) var babbles []*Babble
return store.FilterByIndex("allByPosted", func(b *Babble) bool { searchTerm := "%" + term + "%"
return strings.Contains(strings.ToLower(b.Babble), lowerTerm) err := database.Select(&babbles, "SELECT * FROM babble WHERE babble LIKE %s ORDER BY posted DESC, id DESC", searchTerm)
}), nil return babbles, err
} }
func RecentByAuthor(author string, limit int) ([]*Babble, error) { func RecentByAuthor(authorID int, limit int) ([]*Babble, error) {
messages, err := ByAuthor(author) var babbles []*Babble
if err != nil { err := database.Select(&babbles, "SELECT * FROM babble ORDER BY posted DESC, id DESC LIMIT %d", authorID, limit)
return nil, err return babbles, err
}
if limit > len(messages) {
limit = len(messages)
}
return messages[:limit], nil
} }
// Helper methods
func (b *Babble) PostedTime() time.Time { func (b *Babble) PostedTime() time.Time {
return time.Unix(b.Posted, 0) return time.Unix(b.Posted, 0)
} }
@ -180,8 +120,8 @@ func (b *Babble) Age() time.Duration {
return time.Since(b.PostedTime()) return time.Since(b.PostedTime())
} }
func (b *Babble) IsAuthor(username string) bool { func (b *Babble) IsAuthor(userID int) bool {
return strings.EqualFold(b.Author, username) return b.Author == userID
} }
func (b *Babble) Preview(maxLength int) string { func (b *Babble) Preview(maxLength int) string {
@ -265,14 +205,3 @@ func (b *Babble) HasMention(username string) bool {
} }
return false return false
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -3,16 +3,16 @@ package drops
import ( import (
"fmt" "fmt"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Drop represents a drop item in the game // Drop represents a drop item in the game
type Drop struct { type Drop struct {
ID int `json:"id"` ID int
Name string `json:"name" db:"required"` Name string
Level int `json:"level" db:"index"` Level int
Type int `json:"type" db:"index"` Type int
Att string `json:"att"` Att string
} }
// DropType constants for drop types // DropType constants for drop types
@ -20,43 +20,12 @@ const (
TypeConsumable = 1 TypeConsumable = 1
) )
// Global store // New creates a new Drop with sensible defaults
var store *nigiri.BaseStore[Drop]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Drop]()
// Register custom indices
store.RegisterIndex("byLevel", nigiri.BuildIntGroupIndex(func(d *Drop) int {
return d.Level
}))
store.RegisterIndex("byType", nigiri.BuildIntGroupIndex(func(d *Drop) int {
return d.Type
}))
store.RegisterIndex("allByID", nigiri.BuildSortedListIndex(func(a, b *Drop) bool {
return a.ID < b.ID
}))
store.RebuildIndices()
}
// GetStore returns the drops store
func GetStore() *nigiri.BaseStore[Drop] {
if store == nil {
panic("drops store not initialized - call Initialize first")
}
return store
}
// Creates a new Drop with sensible defaults
func New() *Drop { func New() *Drop {
return &Drop{ return &Drop{
Name: "", Name: "",
Level: 1, // Default minimum level Level: 1,
Type: TypeConsumable, // Default to consumable Type: TypeConsumable,
Att: "", Att: "",
} }
} }
@ -76,54 +45,45 @@ func (d *Drop) Validate() error {
} }
// CRUD operations // CRUD operations
func (d *Drop) Save() error {
if d.ID == 0 {
id, err := store.Create(d)
if err != nil {
return err
}
d.ID = id
return nil
}
return store.Update(d.ID, d)
}
func (d *Drop) Delete() error { func (d *Drop) Delete() error {
store.Remove(d.ID) return database.Exec("DELETE FROM drops WHERE id = %d", d.ID)
return nil
} }
// Insert with ID assignment
func (d *Drop) Insert() error { func (d *Drop) Insert() error {
id, err := store.Create(d) id, err := database.Insert("drops", d, "ID")
if err != nil { if err != nil {
return err return err
} }
d.ID = id d.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Drop, error) { func Find(id int) (*Drop, error) {
drop, exists := store.Find(id) var drop Drop
if !exists { err := database.Get(&drop, "SELECT * FROM drops WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("drop with ID %d not found", id) return nil, fmt.Errorf("drop with ID %d not found", id)
} }
return drop, nil return &drop, nil
} }
func All() ([]*Drop, error) { func All() ([]*Drop, error) {
return store.AllSorted("allByID"), nil var drops []*Drop
err := database.Select(&drops, "SELECT * FROM drops ORDER BY id ASC")
return drops, err
} }
func ByLevel(minLevel int) ([]*Drop, error) { func ByLevel(minLevel int) ([]*Drop, error) {
return store.FilterByIndex("allByID", func(d *Drop) bool { var drops []*Drop
return d.Level <= minLevel err := database.Select(&drops, "SELECT * FROM drops WHERE level <= %d ORDER BY id ASC", minLevel)
}), nil return drops, err
} }
func ByType(dropType int) ([]*Drop, error) { func ByType(dropType int) ([]*Drop, error) {
return store.GroupByIndex("byType", dropType), nil var drops []*Drop
err := database.Select(&drops, "SELECT * FROM drops WHERE type = %d ORDER BY id ASC", dropType)
return drops, err
} }
// Helper methods // Helper methods
@ -139,14 +99,3 @@ func (d *Drop) TypeName() string {
return "Unknown" return "Unknown"
} }
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -0,0 +1,263 @@
package fightlogs
import (
"fmt"
"strings"
"time"
"dk/internal/database"
)
// FightLog represents a single action in a fight
type FightLog struct {
ID int
FightID int
Type int
Data int
Name string
Created int64
}
// Action type constants
const (
ActionAttackHit = 1
ActionAttackMiss = 2
ActionSpellHeal = 3
ActionSpellHurt = 4
ActionRunSuccess = 5
ActionRunFail = 6
ActionGeneric = 7
ActionMonsterAttack = 8
ActionMonsterMiss = 9
ActionMonsterSpell = 10
ActionMonsterDeath = 11
)
// New creates a new FightLog with sensible defaults
func New(fightID int) *FightLog {
return &FightLog{
FightID: fightID,
Type: ActionGeneric,
Data: 0,
Name: "",
Created: time.Now().Unix(),
}
}
// Validate checks if fight log has valid values
func (fl *FightLog) Validate() error {
if fl.FightID <= 0 {
return fmt.Errorf("fight log FightID must be positive")
}
if fl.Created <= 0 {
return fmt.Errorf("fight log Created timestamp must be positive")
}
return nil
}
// CRUD operations
func (fl *FightLog) Delete() error {
return database.Exec("DELETE FROM fight_logs WHERE id = %d", fl.ID)
}
func (fl *FightLog) Insert() error {
id, err := database.Insert("fight_logs", fl, "ID")
if err != nil {
return err
}
fl.ID = int(id)
return nil
}
// Query functions
func Find(id int) (*FightLog, error) {
var log FightLog
err := database.Get(&log, "SELECT * FROM fight_logs WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("fight log with ID %d not found", id)
}
return &log, nil
}
func ByFightID(fightID int) ([]*FightLog, error) {
var logs []*FightLog
err := database.Select(&logs, "SELECT * FROM fight_logs WHERE fight_id = %d ORDER BY created ASC, id ASC", fightID)
return logs, err
}
func DeleteByFightID(fightID int) error {
return database.Exec("DELETE FROM fight_logs WHERE fight_id = %d", fightID)
}
// Helper functions for adding different types of actions
func AddAction(fightID int, action string) error {
log := &FightLog{
FightID: fightID,
Type: ActionGeneric,
Name: action,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddAttackHit(fightID, damage int) error {
log := &FightLog{
FightID: fightID,
Type: ActionAttackHit,
Data: damage,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddAttackMiss(fightID int) error {
log := &FightLog{
FightID: fightID,
Type: ActionAttackMiss,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddSpellHeal(fightID int, spellName string, healAmount int) error {
log := &FightLog{
FightID: fightID,
Type: ActionSpellHeal,
Data: healAmount,
Name: spellName,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddSpellHurt(fightID int, spellName string, damage int) error {
log := &FightLog{
FightID: fightID,
Type: ActionSpellHurt,
Data: damage,
Name: spellName,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddRunSuccess(fightID int) error {
log := &FightLog{
FightID: fightID,
Type: ActionRunSuccess,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddRunFail(fightID int) error {
log := &FightLog{
FightID: fightID,
Type: ActionRunFail,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddMonsterAttack(fightID int, monsterName string, damage int) error {
log := &FightLog{
FightID: fightID,
Type: ActionMonsterAttack,
Data: damage,
Name: monsterName,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddMonsterMiss(fightID int, monsterName string) error {
log := &FightLog{
FightID: fightID,
Type: ActionMonsterMiss,
Name: monsterName,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddMonsterSpell(fightID int, monsterName, spellName string, damage int) error {
log := &FightLog{
FightID: fightID,
Type: ActionMonsterSpell,
Data: damage,
Name: monsterName + "|" + spellName,
Created: time.Now().Unix(),
}
return log.Insert()
}
func AddMonsterDeath(fightID int, monsterName string) error {
log := &FightLog{
FightID: fightID,
Type: ActionMonsterDeath,
Name: monsterName,
Created: time.Now().Unix(),
}
return log.Insert()
}
// Convert logs to human-readable strings
func GetActions(fightID int) ([]string, error) {
logs, err := ByFightID(fightID)
if err != nil {
return nil, err
}
result := make([]string, len(logs))
for i, log := range logs {
result[i] = log.ToString()
}
return result, nil
}
func GetLastAction(fightID int) (string, error) {
var log FightLog
err := database.Get(&log, "SELECT * FROM fight_logs WHERE fight_id = %d ORDER BY created DESC, id DESC LIMIT 1", fightID)
if err != nil {
return "", nil // No logs found
}
return log.ToString(), nil
}
// Helper methods
func (fl *FightLog) CreatedTime() time.Time {
return time.Unix(fl.Created, 0)
}
func (fl *FightLog) ToString() string {
switch fl.Type {
case ActionAttackHit:
return fmt.Sprintf("You attacked for %d damage!", fl.Data)
case ActionAttackMiss:
return "You missed your attack!"
case ActionSpellHeal:
return fmt.Sprintf("You cast %s and healed %d HP!", fl.Name, fl.Data)
case ActionSpellHurt:
return fmt.Sprintf("You cast %s and dealt %d damage!", fl.Name, fl.Data)
case ActionRunSuccess:
return "You successfully ran away!"
case ActionRunFail:
return "You failed to run away!"
case ActionGeneric:
return fl.Name
case ActionMonsterAttack:
return fmt.Sprintf("%s attacks for %d damage!", fl.Name, fl.Data)
case ActionMonsterMiss:
return fmt.Sprintf("%s missed its attack!", fl.Name)
case ActionMonsterSpell:
parts := strings.Split(fl.Name, "|")
if len(parts) == 2 {
return fmt.Sprintf("%s casts %s for %d damage!", parts[0], parts[1], fl.Data)
}
return fmt.Sprintf("%s casts a spell for %d damage!", fl.Name, fl.Data)
case ActionMonsterDeath:
return fmt.Sprintf("%s has been defeated!", fl.Name)
default:
return "Unknown action"
}
}

View File

@ -1,140 +0,0 @@
package fights
import (
"fmt"
"strings"
"time"
)
// ActionEntry represents a compacted fight action log. This allows us to store more logs
// in the same space as a single string.
type ActionEntry struct {
Type int `json:"t"`
Data int `json:"d,omitempty"`
Name string `json:"n,omitempty"` // For spell names
}
// Action type constants
const (
ActionAttackHit = 1
ActionAttackMiss = 2
ActionSpellHeal = 3
ActionSpellHurt = 4
ActionRunSuccess = 5
ActionRunFail = 6
ActionGeneric = 7
ActionMonsterAttack = 8
ActionMonsterMiss = 9
ActionMonsterSpell = 10
ActionMonsterDeath = 11
)
func (f *Fight) AddAction(action string) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionGeneric, Name: action})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionAttackHit(damage int) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionAttackHit, Data: damage})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionAttackMiss() {
f.Actions = append(f.Actions, ActionEntry{Type: ActionAttackMiss})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionSpellHeal(spellName string, healAmount int) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionSpellHeal, Data: healAmount, Name: spellName})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionSpellHurt(spellName string, damage int) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionSpellHurt, Data: damage, Name: spellName})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionRunSuccess() {
f.Actions = append(f.Actions, ActionEntry{Type: ActionRunSuccess})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionRunFail() {
f.Actions = append(f.Actions, ActionEntry{Type: ActionRunFail})
f.Updated = time.Now().Unix()
}
// Convert actions to human-readable strings
func (f *Fight) GetActions() []string {
result := make([]string, len(f.Actions))
for i, action := range f.Actions {
result[i] = f.actionToString(action)
}
return result
}
func (f *Fight) GetLastAction() string {
if len(f.Actions) == 0 {
return ""
}
return f.actionToString(f.Actions[len(f.Actions)-1])
}
func (f *Fight) ClearActions() {
f.Actions = make([]ActionEntry, 0)
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionMonsterAttack(monsterName string, damage int) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterAttack, Data: damage, Name: monsterName})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionMonsterMiss(monsterName string) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterMiss, Name: monsterName})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionMonsterSpell(monsterName, spellName string, damage int) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterSpell, Data: damage, Name: monsterName + "|" + spellName})
f.Updated = time.Now().Unix()
}
func (f *Fight) AddActionMonsterDeath(monsterName string) {
f.Actions = append(f.Actions, ActionEntry{Type: ActionMonsterDeath, Name: monsterName})
f.Updated = time.Now().Unix()
}
// Update actionToString method - add these cases
func (f *Fight) actionToString(action ActionEntry) string {
switch action.Type {
case ActionAttackHit:
return fmt.Sprintf("You attacked for %d damage!", action.Data)
case ActionAttackMiss:
return "You missed your attack!"
case ActionSpellHeal:
return fmt.Sprintf("You cast %s and healed %d HP!", action.Name, action.Data)
case ActionSpellHurt:
return fmt.Sprintf("You cast %s and dealt %d damage!", action.Name, action.Data)
case ActionRunSuccess:
return "You successfully ran away!"
case ActionRunFail:
return "You failed to run away!"
case ActionGeneric:
return action.Name
case ActionMonsterAttack:
return fmt.Sprintf("%s attacks for %d damage!", action.Name, action.Data)
case ActionMonsterMiss:
return fmt.Sprintf("%s missed its attack!", action.Name)
case ActionMonsterSpell:
parts := strings.Split(action.Name, "|")
if len(parts) == 2 {
return fmt.Sprintf("%s casts %s for %d damage!", parts[0], parts[1], action.Data)
}
return fmt.Sprintf("%s casts a spell for %d damage!", action.Name, action.Data)
case ActionMonsterDeath:
return fmt.Sprintf("%s has been defeated!", action.Name)
default:
return "Unknown action"
}
}

View File

@ -4,80 +4,29 @@ import (
"fmt" "fmt"
"time" "time"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Fight represents a fight, past or present // Fight represents a fight, past or present
type Fight struct { type Fight struct {
ID int `json:"id"` ID int
UserID int `json:"user_id" db:"index"` UserID int
MonsterID int `json:"monster_id" db:"index"` MonsterID int
MonsterHP int `json:"monster_hp"` MonsterHP int
MonsterMaxHP int `json:"monster_max_hp"` MonsterMaxHP int
MonsterSleep int `json:"monster_sleep"` MonsterSleep int
MonsterImmune int `json:"monster_immune"` MonsterImmune int
UberDamage int `json:"uber_damage"` UberDamage int
UberDefense int `json:"uber_defense"` UberDefense int
FirstStrike bool `json:"first_strike"` FirstStrike bool
Turn int `json:"turn"` Turn int
RanAway bool `json:"ran_away"` RanAway bool
Victory bool `json:"victory"` Victory bool
Won bool `json:"won"` Won bool
RewardGold int `json:"reward_gold"` RewardGold int
RewardExp int `json:"reward_exp"` RewardExp int
Actions []ActionEntry `json:"actions"` Created int64
Created int64 `json:"created"` Updated int64
Updated int64 `json:"updated"`
}
// Global store
var store *nigiri.BaseStore[Fight]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Fight]()
// Register custom indices
store.RegisterIndex("byUserID", nigiri.BuildIntGroupIndex(func(f *Fight) int {
return f.UserID
}))
store.RegisterIndex("byMonsterID", nigiri.BuildIntGroupIndex(func(f *Fight) int {
return f.MonsterID
}))
store.RegisterIndex("activeFights", nigiri.BuildFilteredIntGroupIndex(
func(f *Fight) bool {
return !f.RanAway && !f.Victory
},
func(f *Fight) int {
return f.UserID
},
))
store.RegisterIndex("allByCreated", nigiri.BuildSortedListIndex(func(a, b *Fight) bool {
if a.Created != b.Created {
return a.Created > b.Created // DESC
}
return a.ID > b.ID // DESC
}))
store.RegisterIndex("allByUpdated", nigiri.BuildSortedListIndex(func(a, b *Fight) bool {
if a.Updated != b.Updated {
return a.Updated > b.Updated // DESC
}
return a.ID > b.ID // DESC
}))
store.RebuildIndices()
}
// GetStore returns the fights store
func GetStore() *nigiri.BaseStore[Fight] {
if store == nil {
panic("fights store not initialized - call Initialize first")
}
return store
} }
// New creates a new Fight with sensible defaults // New creates a new Fight with sensible defaults
@ -99,7 +48,6 @@ func New(userID, monsterID int) *Fight {
Won: false, Won: false,
RewardGold: 0, RewardGold: 0,
RewardExp: 0, RewardExp: 0,
Actions: make([]ActionEntry, 0),
Created: now, Created: now,
Updated: now, Updated: now,
} }
@ -129,75 +77,88 @@ func (f *Fight) Validate() error {
} }
// CRUD operations // CRUD operations
func (f *Fight) Save() error {
f.Updated = time.Now().Unix()
if f.ID == 0 {
id, err := store.Create(f)
if err != nil {
return err
}
f.ID = id
return nil
}
return store.Update(f.ID, f)
}
func (f *Fight) Delete() error { func (f *Fight) Delete() error {
store.Remove(f.ID) return database.Exec("DELETE FROM fights WHERE id = %d", f.ID)
return nil
} }
// Insert with ID assignment
func (f *Fight) Insert() error { func (f *Fight) Insert() error {
f.Updated = time.Now().Unix() f.Updated = time.Now().Unix()
id, err := store.Create(f) id, err := database.Insert("fights", f, "ID")
if err != nil { if err != nil {
return err return err
} }
f.ID = id f.ID = int(id)
return nil return nil
} }
func (f *Fight) Update() error {
f.Updated = time.Now().Unix()
return database.Update("fights", map[string]any{
"user_id": f.UserID,
"monster_id": f.MonsterID,
"monster_hp": f.MonsterHP,
"monster_max_hp": f.MonsterMaxHP,
"monster_sleep": f.MonsterSleep,
"monster_immune": f.MonsterImmune,
"uber_damage": f.UberDamage,
"uber_defense": f.UberDefense,
"first_strike": f.FirstStrike,
"turn": f.Turn,
"ran_away": f.RanAway,
"victory": f.Victory,
"won": f.Won,
"reward_gold": f.RewardGold,
"reward_exp": f.RewardExp,
"created": f.Created,
"updated": f.Updated,
}, "id", f.ID)
}
// Query functions // Query functions
func Find(id int) (*Fight, error) { func Find(id int) (*Fight, error) {
fight, exists := store.Find(id) var fight Fight
if !exists { err := database.Get(&fight, "SELECT * FROM fights WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("fight with ID %d not found", id) return nil, fmt.Errorf("fight with ID %d not found", id)
} }
return fight, nil return &fight, nil
} }
func All() ([]*Fight, error) { func All() ([]*Fight, error) {
return store.AllSorted("allByCreated"), nil var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights ORDER BY created DESC, id DESC")
return fights, err
} }
func ByUserID(userID int) ([]*Fight, error) { func ByUserID(userID int) ([]*Fight, error) {
return store.GroupByIndex("byUserID", userID), nil var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights WHERE user_id = %d ORDER BY created DESC, id DESC", userID)
return fights, err
} }
func ByMonsterID(monsterID int) ([]*Fight, error) { func ByMonsterID(monsterID int) ([]*Fight, error) {
return store.GroupByIndex("byMonsterID", monsterID), nil var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights WHERE monster_id = %d ORDER BY created DESC, id DESC", monsterID)
return fights, err
} }
func ActiveByUserID(userID int) ([]*Fight, error) { func ActiveByUserID(userID int) ([]*Fight, error) {
return store.GroupByIndex("activeFights", userID), nil var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights WHERE user_id = %d AND ran_away = 0 AND victory = 0 ORDER BY created DESC, id DESC", userID)
return fights, err
} }
func Active() ([]*Fight, error) { func Active() ([]*Fight, error) {
result := store.FilterByIndex("allByCreated", func(f *Fight) bool { var fights []*Fight
return !f.RanAway && !f.Victory err := database.Select(&fights, "SELECT * FROM fights WHERE ran_away = 0 AND victory = 0 ORDER BY created DESC, id DESC")
}) return fights, err
return result, nil
} }
func Recent(within time.Duration) ([]*Fight, error) { func Recent(within time.Duration) ([]*Fight, error) {
cutoff := time.Now().Add(-within).Unix() cutoff := time.Now().Add(-within).Unix()
var fights []*Fight
result := store.FilterByIndex("allByCreated", func(f *Fight) bool { err := database.Select(&fights, "SELECT * FROM fights WHERE created >= %d ORDER BY created DESC, id DESC", cutoff)
return f.Created >= cutoff return fights, err
})
return result, nil
} }
// Helper methods // Helper methods

View File

@ -2,67 +2,32 @@ package forum
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"time" "time"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Forum represents a forum post or thread in the game // Forum represents a forum post or thread in the game
type Forum struct { type Forum struct {
ID int `json:"id"` ID int
Posted int64 `json:"posted"` Posted int64
LastPost int64 `json:"last_post"` LastPost int64
Author int `json:"author" db:"index"` Author int
Parent int `json:"parent" db:"index"` Parent int
Replies int `json:"replies"` Replies int
Title string `json:"title" db:"required"` Title string
Content string `json:"content" db:"required"` Content string
} }
// Global store // New creates a new Forum with sensible defaults
var store *nigiri.BaseStore[Forum]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Forum]()
// Register custom indices
store.RegisterIndex("byParent", nigiri.BuildIntGroupIndex(func(f *Forum) int {
return f.Parent
}))
store.RegisterIndex("byAuthor", nigiri.BuildIntGroupIndex(func(f *Forum) int {
return f.Author
}))
store.RegisterIndex("allByLastPost", nigiri.BuildSortedListIndex(func(a, b *Forum) bool {
if a.LastPost != b.LastPost {
return a.LastPost > b.LastPost // DESC
}
return a.ID > b.ID // DESC
}))
store.RebuildIndices()
}
// GetStore returns the forum store
func GetStore() *nigiri.BaseStore[Forum] {
if store == nil {
panic("forum store not initialized - call Initialize first")
}
return store
}
// Creates a new Forum with sensible defaults
func New() *Forum { func New() *Forum {
now := time.Now().Unix() now := time.Now().Unix()
return &Forum{ return &Forum{
Posted: now, Posted: now,
LastPost: now, LastPost: now,
Author: 0, Author: 0,
Parent: 0, // Default to thread (not reply) Parent: 0,
Replies: 0, Replies: 0,
Title: "", Title: "",
Content: "", Content: "",
@ -93,102 +58,77 @@ func (f *Forum) Validate() error {
} }
// CRUD operations // CRUD operations
func (f *Forum) Save() error {
if f.ID == 0 {
id, err := store.Create(f)
if err != nil {
return err
}
f.ID = id
return nil
}
return store.Update(f.ID, f)
}
func (f *Forum) Delete() error { func (f *Forum) Delete() error {
store.Remove(f.ID) return database.Exec("DELETE FROM forum WHERE id = %d", f.ID)
return nil
} }
// Insert with ID assignment
func (f *Forum) Insert() error { func (f *Forum) Insert() error {
id, err := store.Create(f) id, err := database.Insert("forum", f, "ID")
if err != nil { if err != nil {
return err return err
} }
f.ID = id f.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Forum, error) { func Find(id int) (*Forum, error) {
forum, exists := store.Find(id) var forum Forum
if !exists { err := database.Get(&forum, "SELECT * FROM forum WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("forum post with ID %d not found", id) return nil, fmt.Errorf("forum post with ID %d not found", id)
} }
return forum, nil return &forum, nil
} }
func All() ([]*Forum, error) { func All() ([]*Forum, error) {
return store.AllSorted("allByLastPost"), nil var forums []*Forum
err := database.Select(&forums, "SELECT * FROM forum ORDER BY newpostdate DESC, id DESC")
return forums, err
} }
func Threads() ([]*Forum, error) { func Threads() ([]*Forum, error) {
return store.FilterByIndex("allByLastPost", func(f *Forum) bool { var forums []*Forum
return f.Parent == 0 err := database.Select(&forums, "SELECT * FROM forum WHERE parent = 0 ORDER BY newpostdate DESC, id DESC")
}), nil return forums, err
} }
func ByParent(parentID int) ([]*Forum, error) { func ByParent(parentID int) ([]*Forum, error) {
replies := store.GroupByIndex("byParent", parentID) var forums []*Forum
if parentID > 0 {
// Sort replies chronologically (posted ASC, then ID ASC) // Replies sorted chronologically
if parentID > 0 && len(replies) > 1 { err := database.Select(&forums, "SELECT * FROM forum WHERE parent = %d ORDER BY postdate ASC, id ASC", parentID)
sort.Slice(replies, func(i, j int) bool { return forums, err
if replies[i].Posted != replies[j].Posted { } else {
return replies[i].Posted < replies[j].Posted // ASC // Threads sorted by last activity
} err := database.Select(&forums, "SELECT * FROM forum WHERE parent = %d ORDER BY newpostdate DESC, id DESC", parentID)
return replies[i].ID < replies[j].ID // ASC return forums, err
})
} }
return replies, nil
} }
func ByAuthor(authorID int) ([]*Forum, error) { func ByAuthor(authorID int) ([]*Forum, error) {
posts := store.GroupByIndex("byAuthor", authorID) var forums []*Forum
err := database.Select(&forums, "SELECT * FROM forum WHERE author = %d ORDER BY postdate DESC, id DESC", authorID)
// Sort by posted DESC, then ID DESC return forums, err
sort.Slice(posts, func(i, j int) bool {
if posts[i].Posted != posts[j].Posted {
return posts[i].Posted > posts[j].Posted // DESC
}
return posts[i].ID > posts[j].ID // DESC
})
return posts, nil
} }
func Recent(limit int) ([]*Forum, error) { func Recent(limit int) ([]*Forum, error) {
all := store.AllSorted("allByLastPost") var forums []*Forum
if limit > len(all) { err := database.Select(&forums, "SELECT * FROM forum ORDER BY newpostdate DESC, id DESC LIMIT %d", limit)
limit = len(all) return forums, err
}
return all[:limit], nil
} }
func Search(term string) ([]*Forum, error) { func Search(term string) ([]*Forum, error) {
lowerTerm := strings.ToLower(term) var forums []*Forum
return store.FilterByIndex("allByLastPost", func(f *Forum) bool { searchTerm := "%" + term + "%"
return strings.Contains(strings.ToLower(f.Title), lowerTerm) || err := database.Select(&forums, "SELECT * FROM forum WHERE title LIKE %s OR content LIKE %s ORDER BY newpostdate DESC, id DESC", searchTerm, searchTerm)
strings.Contains(strings.ToLower(f.Content), lowerTerm) return forums, err
}), nil
} }
func Since(since int64) ([]*Forum, error) { func Since(since int64) ([]*Forum, error) {
return store.FilterByIndex("allByLastPost", func(f *Forum) bool { var forums []*Forum
return f.LastPost >= since err := database.Select(&forums, "SELECT * FROM forum WHERE newpostdate >= %d ORDER BY newpostdate DESC, id DESC", since)
}), nil return forums, err
} }
// Helper methods // Helper methods
@ -309,14 +249,3 @@ func (f *Forum) GetThread() (*Forum, error) {
} }
return Find(f.Parent) return Find(f.Parent)
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -3,17 +3,17 @@ package items
import ( import (
"fmt" "fmt"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Item represents an item in the game // Item represents an item in the game
type Item struct { type Item struct {
ID int `json:"id"` ID int
Type int `json:"type" db:"index"` Type int
Name string `json:"name" db:"required"` Name string
Value int `json:"value"` Value int
Att int `json:"att"` Att int
Special string `json:"special"` Special string
} }
// ItemType constants for item types // ItemType constants for item types
@ -23,37 +23,10 @@ const (
TypeShield = 3 TypeShield = 3
) )
// Global store // New creates a new Item with sensible defaults
var store *nigiri.BaseStore[Item]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Item]()
// Register custom indices
store.RegisterIndex("byType", nigiri.BuildIntGroupIndex(func(i *Item) int {
return i.Type
}))
store.RegisterIndex("allByID", nigiri.BuildSortedListIndex(func(a, b *Item) bool {
return a.ID < b.ID
}))
store.RebuildIndices()
}
// GetStore returns the items store
func GetStore() *nigiri.BaseStore[Item] {
if store == nil {
panic("items store not initialized - call Initialize first")
}
return store
}
// Creates a new Item with sensible defaults
func New() *Item { func New() *Item {
return &Item{ return &Item{
Type: TypeWeapon, // Default to weapon Type: TypeWeapon,
Name: "", Name: "",
Value: 0, Value: 0,
Att: 0, Att: 0,
@ -79,48 +52,39 @@ func (i *Item) Validate() error {
} }
// CRUD operations // CRUD operations
func (i *Item) Save() error {
if i.ID == 0 {
id, err := store.Create(i)
if err != nil {
return err
}
i.ID = id
return nil
}
return store.Update(i.ID, i)
}
func (i *Item) Delete() error { func (i *Item) Delete() error {
store.Remove(i.ID) return database.Exec("DELETE FROM items WHERE id = %d", i.ID)
return nil
} }
// Insert with ID assignment
func (i *Item) Insert() error { func (i *Item) Insert() error {
id, err := store.Create(i) id, err := database.Insert("items", i, "ID")
if err != nil { if err != nil {
return err return err
} }
i.ID = id i.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Item, error) { func Find(id int) (*Item, error) {
item, exists := store.Find(id) var item Item
if !exists { err := database.Get(&item, "SELECT * FROM items WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("item with ID %d not found", id) return nil, fmt.Errorf("item with ID %d not found", id)
} }
return item, nil return &item, nil
} }
func All() ([]*Item, error) { func All() ([]*Item, error) {
return store.AllSorted("allByID"), nil var items []*Item
err := database.Select(&items, "SELECT * FROM items ORDER BY id ASC")
return items, err
} }
func ByType(itemType int) ([]*Item, error) { func ByType(itemType int) ([]*Item, error) {
return store.GroupByIndex("byType", itemType), nil var items []*Item
err := database.Select(&items, "SELECT * FROM items WHERE type = %d ORDER BY id ASC", itemType)
return items, err
} }
// Helper methods // Helper methods
@ -156,14 +120,3 @@ func (i *Item) HasSpecial() bool {
func (i *Item) IsEquippable() bool { func (i *Item) IsEquippable() bool {
return i.Type == TypeWeapon || i.Type == TypeArmor || i.Type == TypeShield return i.Type == TypeWeapon || i.Type == TypeArmor || i.Type == TypeShield
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -3,20 +3,20 @@ package monsters
import ( import (
"fmt" "fmt"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Monster represents a monster in the game // Monster represents a monster in the game
type Monster struct { type Monster struct {
ID int `json:"id"` ID int
Name string `json:"name" db:"required"` Name string
MaxHP int `json:"max_hp"` MaxHP int
MaxDmg int `json:"max_dmg"` MaxDmg int
Armor int `json:"armor"` Armor int
Level int `json:"level" db:"index"` Level int
MaxExp int `json:"max_exp"` MaxExp int
MaxGold int `json:"max_gold"` MaxGold int
Immune int `json:"immune" db:"index"` Immune int
} }
// Immunity constants // Immunity constants
@ -26,41 +26,7 @@ const (
ImmuneSleep = 2 ImmuneSleep = 2
) )
// Global store // New creates a new Monster with sensible defaults
var store *nigiri.BaseStore[Monster]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Monster]()
// Register custom indices
store.RegisterIndex("byLevel", nigiri.BuildIntGroupIndex(func(m *Monster) int {
return m.Level
}))
store.RegisterIndex("byImmunity", nigiri.BuildIntGroupIndex(func(m *Monster) int {
return m.Immune
}))
store.RegisterIndex("allByLevel", nigiri.BuildSortedListIndex(func(a, b *Monster) bool {
if a.Level == b.Level {
return a.ID < b.ID
}
return a.Level < b.Level
}))
store.RebuildIndices()
}
// GetStore returns the monsters store
func GetStore() *nigiri.BaseStore[Monster] {
if store == nil {
panic("monsters store not initialized - call Initialize first")
}
return store
}
// Creates a new Monster with sensible defaults
func New() *Monster { func New() *Monster {
return &Monster{ return &Monster{
Name: "", Name: "",
@ -92,61 +58,51 @@ func (m *Monster) Validate() error {
} }
// CRUD operations // CRUD operations
func (m *Monster) Save() error {
if m.ID == 0 {
id, err := store.Create(m)
if err != nil {
return err
}
m.ID = id
return nil
}
return store.Update(m.ID, m)
}
func (m *Monster) Delete() error { func (m *Monster) Delete() error {
store.Remove(m.ID) return database.Exec("DELETE FROM monsters WHERE id = %d", m.ID)
return nil
} }
// Insert with ID assignment
func (m *Monster) Insert() error { func (m *Monster) Insert() error {
id, err := store.Create(m) id, err := database.Insert("monsters", m, "ID")
if err != nil { if err != nil {
return err return err
} }
m.ID = id m.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Monster, error) { func Find(id int) (*Monster, error) {
monster, exists := store.Find(id) var monster Monster
if !exists { err := database.Get(&monster, "SELECT * FROM monsters WHERE id = %d", id)
if err != nil {
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
} }
func All() ([]*Monster, error) { func All() ([]*Monster, error) {
return store.AllSorted("allByLevel"), nil var monsters []*Monster
err := database.Select(&monsters, "SELECT * FROM monsters ORDER BY level ASC, id ASC")
return monsters, err
} }
func ByLevel(level int) ([]*Monster, error) { func ByLevel(level int) ([]*Monster, error) {
return store.GroupByIndex("byLevel", level), nil var monsters []*Monster
err := database.Select(&monsters, "SELECT * FROM monsters WHERE level = %d ORDER BY id ASC", level)
return monsters, err
} }
func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) { func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) {
var result []*Monster var monsters []*Monster
for level := minLevel; level <= maxLevel; level++ { err := database.Select(&monsters, "SELECT * FROM monsters WHERE level >= %d AND level <= %d ORDER BY level ASC, id ASC", minLevel, maxLevel)
monsters := store.GroupByIndex("byLevel", level) return monsters, err
result = append(result, monsters...)
}
return result, nil
} }
func ByImmunity(immunityType int) ([]*Monster, error) { func ByImmunity(immunityType int) ([]*Monster, error) {
return store.GroupByIndex("byImmunity", immunityType), nil var monsters []*Monster
err := database.Select(&monsters, "SELECT * FROM monsters WHERE immune = %d ORDER BY level ASC, id ASC", immunityType)
return monsters, err
} }
// Helper methods // Helper methods
@ -195,14 +151,3 @@ func (m *Monster) GoldPerHP() float64 {
} }
return float64(m.MaxGold) / float64(m.MaxHP) return float64(m.MaxGold) / float64(m.MaxHP)
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -5,53 +5,23 @@ import (
"strings" "strings"
"time" "time"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// News represents a news post in the game // News represents a news post in the game
type News struct { type News struct {
ID int `json:"id"` ID int
Author int `json:"author" db:"index"` Author int
Posted int64 `json:"posted"` Posted int64
Content string `json:"content" db:"required"` Content string
} }
// Global store // New creates a new News with sensible defaults
var store *nigiri.BaseStore[News]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[News]()
// Register custom indices
store.RegisterIndex("byAuthor", nigiri.BuildIntGroupIndex(func(n *News) int {
return n.Author
}))
store.RegisterIndex("allByPosted", nigiri.BuildSortedListIndex(func(a, b *News) bool {
if a.Posted != b.Posted {
return a.Posted > b.Posted // DESC
}
return a.ID > b.ID // DESC
}))
store.RebuildIndices()
}
// GetStore returns the news store
func GetStore() *nigiri.BaseStore[News] {
if store == nil {
panic("news store not initialized - call Init first")
}
return store
}
// Creates a new News with sensible defaults
func New() *News { func New() *News {
return &News{ return &News{
Author: 0, // No author by default Author: 0,
Posted: time.Now().Unix(), // Current time Posted: time.Now().Unix(),
Content: "", // Empty content Content: "",
} }
} }
@ -67,75 +37,64 @@ func (n *News) Validate() error {
} }
// CRUD operations // CRUD operations
func (n *News) Save() error {
if n.ID == 0 {
id, err := store.Create(n)
if err != nil {
return err
}
n.ID = id
return nil
}
return store.Update(n.ID, n)
}
func (n *News) Delete() error { func (n *News) Delete() error {
store.Remove(n.ID) return database.Exec("DELETE FROM news WHERE id = %d", n.ID)
return nil
} }
// Insert with ID assignment
func (n *News) Insert() error { func (n *News) Insert() error {
id, err := store.Create(n) id, err := database.Insert("news", n, "ID")
if err != nil { if err != nil {
return err return err
} }
n.ID = id n.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*News, error) { func Find(id int) (*News, error) {
news, exists := store.Find(id) var news News
if !exists { err := database.Get(&news, "SELECT * FROM news WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("news with ID %d not found", id) return nil, fmt.Errorf("news with ID %d not found", id)
} }
return news, nil return &news, nil
} }
func All() ([]*News, error) { func All() ([]*News, error) {
return store.AllSorted("allByPosted"), nil var news []*News
err := database.Select(&news, "SELECT * FROM news ORDER BY posted DESC, id DESC")
return news, err
} }
func ByAuthor(authorID int) ([]*News, error) { func ByAuthor(authorID int) ([]*News, error) {
return store.GroupByIndex("byAuthor", authorID), nil var news []*News
err := database.Select(&news, "SELECT * FROM news WHERE author = %d ORDER BY posted DESC, id DESC", authorID)
return news, err
} }
func Recent(limit int) ([]*News, error) { func Recent(limit int) ([]*News, error) {
all := store.AllSorted("allByPosted") var news []*News
if limit > len(all) { err := database.Select(&news, "SELECT * FROM news ORDER BY posted DESC, id DESC LIMIT %d", limit)
limit = len(all) return news, err
}
return all[:limit], nil
} }
func Since(since int64) ([]*News, error) { func Since(since int64) ([]*News, error) {
return store.FilterByIndex("allByPosted", func(n *News) bool { var news []*News
return n.Posted >= since err := database.Select(&news, "SELECT * FROM news WHERE posted >= %d ORDER BY posted DESC, id DESC", since)
}), nil return news, err
} }
func Between(start, end int64) ([]*News, error) { func Between(start, end int64) ([]*News, error) {
return store.FilterByIndex("allByPosted", func(n *News) bool { var news []*News
return n.Posted >= start && n.Posted <= end err := database.Select(&news, "SELECT * FROM news WHERE posted >= %d AND posted <= %d ORDER BY posted DESC, id DESC", start, end)
}), nil return news, err
} }
func Search(term string) ([]*News, error) { func Search(term string) ([]*News, error) {
lowerTerm := strings.ToLower(term) var news []*News
return store.FilterByIndex("allByPosted", func(n *News) bool { searchTerm := "%" + term + "%"
return strings.Contains(strings.ToLower(n.Content), lowerTerm) err := database.Select(&news, "SELECT * FROM news WHERE content LIKE %s ORDER BY posted DESC, id DESC", searchTerm)
}), nil return news, err
} }
// Helper methods // Helper methods
@ -213,14 +172,3 @@ func (n *News) Contains(term string) bool {
func (n *News) IsEmpty() bool { func (n *News) IsEmpty() bool {
return strings.TrimSpace(n.Content) == "" return strings.TrimSpace(n.Content) == ""
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

View File

@ -2,18 +2,17 @@ package spells
import ( import (
"fmt" "fmt"
"strings"
nigiri "git.sharkk.net/Sharkk/Nigiri" "dk/internal/database"
) )
// Spell represents a spell in the game // Spell represents a spell in the game
type Spell struct { type Spell struct {
ID int `json:"id"` ID int
Name string `json:"name" db:"required,unique"` Name string
MP int `json:"mp" db:"index"` MP int
Attribute int `json:"attribute"` Attribute int
Type int `json:"type" db:"index"` Type int
} }
// SpellType constants for spell types // SpellType constants for spell types
@ -25,54 +24,13 @@ const (
TypeDefenseBoost = 5 TypeDefenseBoost = 5
) )
// Global store // New creates a new Spell with sensible defaults
var store *nigiri.BaseStore[Spell]
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Spell]()
// Register custom indices
store.RegisterIndex("byType", nigiri.BuildIntGroupIndex(func(s *Spell) int {
return s.Type
}))
store.RegisterIndex("byName", nigiri.BuildCaseInsensitiveLookupIndex(func(s *Spell) string {
return s.Name
}))
store.RegisterIndex("byMP", nigiri.BuildIntGroupIndex(func(s *Spell) int {
return s.MP
}))
store.RegisterIndex("allByTypeMP", nigiri.BuildSortedListIndex(func(a, b *Spell) bool {
if a.Type != b.Type {
return a.Type < b.Type
}
if a.MP != b.MP {
return a.MP < b.MP
}
return a.ID < b.ID
}))
store.RebuildIndices()
}
// GetStore returns the spells store
func GetStore() *nigiri.BaseStore[Spell] {
if store == nil {
panic("spells store not initialized - call Initialize first")
}
return store
}
// Creates a new Spell with sensible defaults
func New() *Spell { func New() *Spell {
return &Spell{ return &Spell{
Name: "", Name: "",
MP: 5, // Default MP cost MP: 5,
Attribute: 10, // Default attribute value Attribute: 10,
Type: TypeHealing, // Default to healing spell Type: TypeHealing,
} }
} }
@ -94,68 +52,60 @@ func (s *Spell) Validate() error {
} }
// CRUD operations // CRUD operations
func (s *Spell) Save() error {
if s.ID == 0 {
id, err := store.Create(s)
if err != nil {
return err
}
s.ID = id
return nil
}
return store.Update(s.ID, s)
}
func (s *Spell) Delete() error { func (s *Spell) Delete() error {
store.Remove(s.ID) return database.Exec("DELETE FROM spells WHERE id = %d", s.ID)
return nil
} }
// Insert with ID assignment
func (s *Spell) Insert() error { func (s *Spell) Insert() error {
id, err := store.Create(s) id, err := database.Insert("spells", s, "ID")
if err != nil { if err != nil {
return err return err
} }
s.ID = id s.ID = int(id)
return nil return nil
} }
// Query functions // Query functions
func Find(id int) (*Spell, error) { func Find(id int) (*Spell, error) {
spell, exists := store.Find(id) var spell Spell
if !exists { err := database.Get(&spell, "SELECT * FROM spells WHERE id = %d", id)
if err != nil {
return nil, fmt.Errorf("spell with ID %d not found", id) return nil, fmt.Errorf("spell with ID %d not found", id)
} }
return spell, nil return &spell, nil
} }
func All() ([]*Spell, error) { func All() ([]*Spell, error) {
return store.AllSorted("allByTypeMP"), nil var spells []*Spell
err := database.Select(&spells, "SELECT * FROM spells ORDER BY type ASC, mp ASC, id ASC")
return spells, err
} }
func ByType(spellType int) ([]*Spell, error) { func ByType(spellType int) ([]*Spell, error) {
return store.GroupByIndex("byType", spellType), nil var spells []*Spell
err := database.Select(&spells, "SELECT * FROM spells WHERE type = %d ORDER BY mp ASC, id ASC", spellType)
return spells, err
} }
func ByMaxMP(maxMP int) ([]*Spell, error) { func ByMaxMP(maxMP int) ([]*Spell, error) {
return store.FilterByIndex("allByTypeMP", func(s *Spell) bool { var spells []*Spell
return s.MP <= maxMP err := database.Select(&spells, "SELECT * FROM spells WHERE mp <= %d ORDER BY type ASC, mp ASC, id ASC", maxMP)
}), nil return spells, err
} }
func ByTypeAndMaxMP(spellType, maxMP int) ([]*Spell, error) { func ByTypeAndMaxMP(spellType, maxMP int) ([]*Spell, error) {
return store.FilterByIndex("allByTypeMP", func(s *Spell) bool { var spells []*Spell
return s.Type == spellType && s.MP <= maxMP err := database.Select(&spells, "SELECT * FROM spells WHERE type = %d AND mp <= %d ORDER BY mp ASC, id ASC", spellType, maxMP)
}), nil return spells, err
} }
func ByName(name string) (*Spell, error) { func ByName(name string) (*Spell, error) {
spell, exists := store.LookupByIndex("byName", strings.ToLower(name)) var spell Spell
if !exists { err := database.Get(&spell, "SELECT * FROM spells WHERE name = %s COLLATE NOCASE", name)
if err != nil {
return nil, fmt.Errorf("spell with name '%s' not found", name) return nil, fmt.Errorf("spell with name '%s' not found", name)
} }
return spell, nil return &spell, nil
} }
// Helper methods // Helper methods
@ -214,14 +164,3 @@ func (s *Spell) IsOffensive() bool {
func (s *Spell) IsSupport() bool { func (s *Spell) IsSupport() bool {
return s.Type == TypeHealing || s.Type == TypeAttackBoost || s.Type == TypeDefenseBoost return s.Type == TypeHealing || s.Type == TypeAttackBoost || s.Type == TypeDefenseBoost
} }
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}

48
main.go
View File

@ -9,21 +9,10 @@ import (
"path/filepath" "path/filepath"
"syscall" "syscall"
"dk/internal/models/babble"
"dk/internal/models/control"
"dk/internal/models/drops"
"dk/internal/models/fights"
"dk/internal/models/forum"
"dk/internal/models/items"
"dk/internal/models/monsters"
"dk/internal/models/news"
"dk/internal/models/spells"
"dk/internal/models/towns"
"dk/internal/models/users" "dk/internal/models/users"
"dk/internal/routes" "dk/internal/routes"
"dk/internal/template" "dk/internal/template"
nigiri "git.sharkk.net/Sharkk/Nigiri"
sushi "git.sharkk.net/Sharkk/Sushi" sushi "git.sharkk.net/Sharkk/Sushi"
"git.sharkk.net/Sharkk/Sushi/auth" "git.sharkk.net/Sharkk/Sushi/auth"
"git.sharkk.net/Sharkk/Sushi/csrf" "git.sharkk.net/Sharkk/Sushi/csrf"
@ -68,11 +57,6 @@ func start(port string) error {
template.InitializeCache(cwd) template.InitializeCache(cwd)
db := nigiri.NewCollection(filepath.Join(cwd, "data"))
if err := setupStores(db); err != nil {
return fmt.Errorf("failed to setup Nigiri stores: %w", err)
}
app := sushi.New() app := sushi.New()
sushi.InitSessions(filepath.Join(cwd, "data/_sessions.json")) sushi.InitSessions(filepath.Join(cwd, "data/_sessions.json"))
@ -108,11 +92,6 @@ func start(port string) error {
<-c <-c
log.Println("\nReceived shutdown signal, shutting down gracefully...") log.Println("\nReceived shutdown signal, shutting down gracefully...")
log.Println("Saving database...")
if err := db.Save(); err != nil {
log.Printf("Error saving database: %v", err)
}
log.Println("Saving sessions...") log.Println("Saving sessions...")
sushi.SaveSessions() sushi.SaveSessions()
@ -120,33 +99,6 @@ func start(port string) error {
return nil return nil
} }
func setupStores(db *nigiri.Collection) error {
users.Init(db)
towns.Init(db)
spells.Init(db)
news.Init(db)
monsters.Init(db)
items.Init(db)
forum.Init(db)
drops.Init(db)
babble.Init(db)
fights.Init(db)
control.Init(db)
db.Add("users", users.GetStore())
db.Add("towns", towns.GetStore())
db.Add("spells", spells.GetStore())
db.Add("news", news.GetStore())
db.Add("monsters", monsters.GetStore())
db.Add("items", items.GetStore())
db.Add("forum", forum.GetStore())
db.Add("drops", drops.GetStore())
db.Add("babble", babble.GetStore())
db.Add("fights", fights.GetStore())
return nil
}
func getUserByID(userID int) any { func getUserByID(userID int) any {
return users.GetByID(userID) return users.GetByID(userID)
} }