Compare commits

..

No commits in common. "3df8f29a4cc263906d3dbc40d3a92065f45bc79f" and "412baeb46d50092dbc24db329eeaaa73cb8dd520" have entirely different histories.

13 changed files with 1004 additions and 1013 deletions

View File

@ -1,350 +0,0 @@
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

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

@ -1,16 +1,16 @@
package control
import (
"encoding/json"
"fmt"
"os"
"sync"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
var (
global *Control
mu sync.RWMutex
filename string
store *nigiri.BaseStore[Control]
global *Control
mu sync.RWMutex
)
// Control represents the game control settings
@ -24,72 +24,39 @@ type Control struct {
Class3Name string `json:"class_3_name"`
}
// Init loads control settings from the specified JSON file
func Init(jsonFile string) error {
mu.Lock()
defer mu.Unlock()
// Init sets up the Nigiri store for control settings
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Control]()
filename = jsonFile
// Try to load from file
if data, err := os.ReadFile(filename); err == nil {
var ctrl Control
if err := json.Unmarshal(data, &ctrl); err != nil {
return fmt.Errorf("failed to parse JSON: %w", err)
// Load or create the singleton control instance
all := store.GetAll()
if len(all) == 0 {
// Create default control settings
global = New()
global.ID = 1
store.Add(1, global)
} else {
// Use the first (and only) control entry
for _, ctrl := range all {
global = ctrl
break
}
// Apply defaults for any missing fields
defaults := New()
if ctrl.WorldSize == 0 {
ctrl.WorldSize = defaults.WorldSize
if global.WorldSize == 0 {
global.WorldSize = defaults.WorldSize
}
if ctrl.Class1Name == "" {
ctrl.Class1Name = defaults.Class1Name
if global.Class1Name == "" {
global.Class1Name = defaults.Class1Name
}
if ctrl.Class2Name == "" {
ctrl.Class2Name = defaults.Class2Name
if global.Class2Name == "" {
global.Class2Name = defaults.Class2Name
}
if ctrl.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)
if global.Class3Name == "" {
global.Class3Name = defaults.Class3Name
}
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
@ -110,12 +77,12 @@ func Get() *Control {
mu.RLock()
defer mu.RUnlock()
if global == nil {
panic("control not initialized - call Init first")
panic("control not initialized - call Initialize first")
}
return global
}
// Set updates the global control instance and saves to file (thread-safe)
// Set updates the global control instance (thread-safe)
func Set(control *Control) error {
mu.Lock()
defer mu.Unlock()
@ -125,11 +92,15 @@ func Set(control *Control) error {
return err
}
if err := store.Update(1, control); err != nil {
return err
}
global = control
return save()
return nil
}
// Update updates specific fields of the control settings and saves to file
// Update updates specific fields of the control settings
func Update(updater func(*Control)) error {
mu.Lock()
defer mu.Unlock()
@ -142,20 +113,7 @@ func Update(updater func(*Control)) error {
return err
}
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 {
if err := store.Update(1, &updated); err != nil {
return err
}
@ -276,3 +234,14 @@ func (c *Control) GetWorldBounds() (minX, minY, maxX, maxY int) {
radius := c.GetWorldRadius()
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

@ -3,16 +3,16 @@ package drops
import (
"fmt"
"dk/internal/database"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
// Drop represents a drop item in the game
type Drop struct {
ID int
Name string
Level int
Type int
Att string
ID int `json:"id"`
Name string `json:"name" db:"required"`
Level int `json:"level" db:"index"`
Type int `json:"type" db:"index"`
Att string `json:"att"`
}
// DropType constants for drop types
@ -20,12 +20,43 @@ const (
TypeConsumable = 1
)
// New creates a new Drop with sensible defaults
// Global store
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 {
return &Drop{
Name: "",
Level: 1,
Type: TypeConsumable,
Level: 1, // Default minimum level
Type: TypeConsumable, // Default to consumable
Att: "",
}
}
@ -45,45 +76,54 @@ func (d *Drop) Validate() error {
}
// CRUD operations
func (d *Drop) Delete() error {
return database.Exec("DELETE FROM drops WHERE id = %d", d.ID)
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 {
store.Remove(d.ID)
return nil
}
// Insert with ID assignment
func (d *Drop) Insert() error {
id, err := database.Insert("drops", d, "ID")
id, err := store.Create(d)
if err != nil {
return err
}
d.ID = int(id)
d.ID = id
return nil
}
// Query functions
func Find(id int) (*Drop, error) {
var drop Drop
err := database.Get(&drop, "SELECT * FROM drops WHERE id = %d", id)
if err != nil {
drop, exists := store.Find(id)
if !exists {
return nil, fmt.Errorf("drop with ID %d not found", id)
}
return &drop, nil
return drop, nil
}
func All() ([]*Drop, error) {
var drops []*Drop
err := database.Select(&drops, "SELECT * FROM drops ORDER BY id ASC")
return drops, err
return store.AllSorted("allByID"), nil
}
func ByLevel(minLevel int) ([]*Drop, error) {
var drops []*Drop
err := database.Select(&drops, "SELECT * FROM drops WHERE level <= %d ORDER BY id ASC", minLevel)
return drops, err
return store.FilterByIndex("allByID", func(d *Drop) bool {
return d.Level <= minLevel
}), nil
}
func ByType(dropType int) ([]*Drop, error) {
var drops []*Drop
err := database.Select(&drops, "SELECT * FROM drops WHERE type = %d ORDER BY id ASC", dropType)
return drops, err
return store.GroupByIndex("byType", dropType), nil
}
// Helper methods
@ -99,3 +139,14 @@ func (d *Drop) TypeName() string {
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

@ -1,263 +0,0 @@
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

@ -0,0 +1,140 @@
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,29 +4,80 @@ import (
"fmt"
"time"
"dk/internal/database"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
// Fight represents a fight, past or present
type Fight struct {
ID int
UserID int
MonsterID int
MonsterHP int
MonsterMaxHP int
MonsterSleep int
MonsterImmune int
UberDamage int
UberDefense int
FirstStrike bool
Turn int
RanAway bool
Victory bool
Won bool
RewardGold int
RewardExp int
Created int64
Updated int64
ID int `json:"id"`
UserID int `json:"user_id" db:"index"`
MonsterID int `json:"monster_id" db:"index"`
MonsterHP int `json:"monster_hp"`
MonsterMaxHP int `json:"monster_max_hp"`
MonsterSleep int `json:"monster_sleep"`
MonsterImmune int `json:"monster_immune"`
UberDamage int `json:"uber_damage"`
UberDefense int `json:"uber_defense"`
FirstStrike bool `json:"first_strike"`
Turn int `json:"turn"`
RanAway bool `json:"ran_away"`
Victory bool `json:"victory"`
Won bool `json:"won"`
RewardGold int `json:"reward_gold"`
RewardExp int `json:"reward_exp"`
Actions []ActionEntry `json:"actions"`
Created int64 `json:"created"`
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
@ -48,6 +99,7 @@ func New(userID, monsterID int) *Fight {
Won: false,
RewardGold: 0,
RewardExp: 0,
Actions: make([]ActionEntry, 0),
Created: now,
Updated: now,
}
@ -77,88 +129,75 @@ func (f *Fight) Validate() error {
}
// CRUD operations
func (f *Fight) Delete() error {
return database.Exec("DELETE FROM fights WHERE id = %d", f.ID)
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) Insert() error {
f.Updated = time.Now().Unix()
id, err := database.Insert("fights", f, "ID")
if err != nil {
return err
}
f.ID = int(id)
func (f *Fight) Delete() error {
store.Remove(f.ID)
return nil
}
func (f *Fight) Update() error {
// Insert with ID assignment
func (f *Fight) Insert() 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)
id, err := store.Create(f)
if err != nil {
return err
}
f.ID = id
return nil
}
// Query functions
func Find(id int) (*Fight, error) {
var fight Fight
err := database.Get(&fight, "SELECT * FROM fights WHERE id = %d", id)
if err != nil {
fight, exists := store.Find(id)
if !exists {
return nil, fmt.Errorf("fight with ID %d not found", id)
}
return &fight, nil
return fight, nil
}
func All() ([]*Fight, error) {
var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights ORDER BY created DESC, id DESC")
return fights, err
return store.AllSorted("allByCreated"), nil
}
func ByUserID(userID int) ([]*Fight, error) {
var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights WHERE user_id = %d ORDER BY created DESC, id DESC", userID)
return fights, err
return store.GroupByIndex("byUserID", userID), nil
}
func ByMonsterID(monsterID int) ([]*Fight, error) {
var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights WHERE monster_id = %d ORDER BY created DESC, id DESC", monsterID)
return fights, err
return store.GroupByIndex("byMonsterID", monsterID), nil
}
func ActiveByUserID(userID int) ([]*Fight, error) {
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
return store.GroupByIndex("activeFights", userID), nil
}
func Active() ([]*Fight, error) {
var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights WHERE ran_away = 0 AND victory = 0 ORDER BY created DESC, id DESC")
return fights, err
result := store.FilterByIndex("allByCreated", func(f *Fight) bool {
return !f.RanAway && !f.Victory
})
return result, nil
}
func Recent(within time.Duration) ([]*Fight, error) {
cutoff := time.Now().Add(-within).Unix()
var fights []*Fight
err := database.Select(&fights, "SELECT * FROM fights WHERE created >= %d ORDER BY created DESC, id DESC", cutoff)
return fights, err
result := store.FilterByIndex("allByCreated", func(f *Fight) bool {
return f.Created >= cutoff
})
return result, nil
}
// Helper methods

View File

@ -2,32 +2,67 @@ package forum
import (
"fmt"
"sort"
"strings"
"time"
"dk/internal/database"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
// Forum represents a forum post or thread in the game
type Forum struct {
ID int
Posted int64
LastPost int64
Author int
Parent int
Replies int
Title string
Content string
ID int `json:"id"`
Posted int64 `json:"posted"`
LastPost int64 `json:"last_post"`
Author int `json:"author" db:"index"`
Parent int `json:"parent" db:"index"`
Replies int `json:"replies"`
Title string `json:"title" db:"required"`
Content string `json:"content" db:"required"`
}
// New creates a new Forum with sensible defaults
// Global store
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 {
now := time.Now().Unix()
return &Forum{
Posted: now,
LastPost: now,
Author: 0,
Parent: 0,
Parent: 0, // Default to thread (not reply)
Replies: 0,
Title: "",
Content: "",
@ -58,77 +93,102 @@ func (f *Forum) Validate() error {
}
// CRUD operations
func (f *Forum) Delete() error {
return database.Exec("DELETE FROM forum WHERE id = %d", f.ID)
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 {
store.Remove(f.ID)
return nil
}
// Insert with ID assignment
func (f *Forum) Insert() error {
id, err := database.Insert("forum", f, "ID")
id, err := store.Create(f)
if err != nil {
return err
}
f.ID = int(id)
f.ID = id
return nil
}
// Query functions
func Find(id int) (*Forum, error) {
var forum Forum
err := database.Get(&forum, "SELECT * FROM forum WHERE id = %d", id)
if err != nil {
forum, exists := store.Find(id)
if !exists {
return nil, fmt.Errorf("forum post with ID %d not found", id)
}
return &forum, nil
return forum, nil
}
func All() ([]*Forum, error) {
var forums []*Forum
err := database.Select(&forums, "SELECT * FROM forum ORDER BY newpostdate DESC, id DESC")
return forums, err
return store.AllSorted("allByLastPost"), nil
}
func Threads() ([]*Forum, error) {
var forums []*Forum
err := database.Select(&forums, "SELECT * FROM forum WHERE parent = 0 ORDER BY newpostdate DESC, id DESC")
return forums, err
return store.FilterByIndex("allByLastPost", func(f *Forum) bool {
return f.Parent == 0
}), nil
}
func ByParent(parentID int) ([]*Forum, error) {
var forums []*Forum
if parentID > 0 {
// Replies sorted chronologically
err := database.Select(&forums, "SELECT * FROM forum WHERE parent = %d ORDER BY postdate ASC, id ASC", parentID)
return forums, err
} else {
// Threads sorted by last activity
err := database.Select(&forums, "SELECT * FROM forum WHERE parent = %d ORDER BY newpostdate DESC, id DESC", parentID)
return forums, err
replies := store.GroupByIndex("byParent", parentID)
// Sort replies chronologically (posted ASC, then ID ASC)
if parentID > 0 && len(replies) > 1 {
sort.Slice(replies, func(i, j int) bool {
if replies[i].Posted != replies[j].Posted {
return replies[i].Posted < replies[j].Posted // ASC
}
return replies[i].ID < replies[j].ID // ASC
})
}
return replies, nil
}
func ByAuthor(authorID int) ([]*Forum, error) {
var forums []*Forum
err := database.Select(&forums, "SELECT * FROM forum WHERE author = %d ORDER BY postdate DESC, id DESC", authorID)
return forums, err
posts := store.GroupByIndex("byAuthor", authorID)
// Sort by posted DESC, then ID DESC
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) {
var forums []*Forum
err := database.Select(&forums, "SELECT * FROM forum ORDER BY newpostdate DESC, id DESC LIMIT %d", limit)
return forums, err
all := store.AllSorted("allByLastPost")
if limit > len(all) {
limit = len(all)
}
return all[:limit], nil
}
func Search(term string) ([]*Forum, error) {
var forums []*Forum
searchTerm := "%" + term + "%"
err := database.Select(&forums, "SELECT * FROM forum WHERE title LIKE %s OR content LIKE %s ORDER BY newpostdate DESC, id DESC", searchTerm, searchTerm)
return forums, err
lowerTerm := strings.ToLower(term)
return store.FilterByIndex("allByLastPost", func(f *Forum) bool {
return strings.Contains(strings.ToLower(f.Title), lowerTerm) ||
strings.Contains(strings.ToLower(f.Content), lowerTerm)
}), nil
}
func Since(since int64) ([]*Forum, error) {
var forums []*Forum
err := database.Select(&forums, "SELECT * FROM forum WHERE newpostdate >= %d ORDER BY newpostdate DESC, id DESC", since)
return forums, err
return store.FilterByIndex("allByLastPost", func(f *Forum) bool {
return f.LastPost >= since
}), nil
}
// Helper methods
@ -249,3 +309,14 @@ func (f *Forum) GetThread() (*Forum, error) {
}
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 (
"fmt"
"dk/internal/database"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
// Item represents an item in the game
type Item struct {
ID int
Type int
Name string
Value int
Att int
Special string
ID int `json:"id"`
Type int `json:"type" db:"index"`
Name string `json:"name" db:"required"`
Value int `json:"value"`
Att int `json:"att"`
Special string `json:"special"`
}
// ItemType constants for item types
@ -23,10 +23,37 @@ const (
TypeShield = 3
)
// New creates a new Item with sensible defaults
// Global store
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 {
return &Item{
Type: TypeWeapon,
Type: TypeWeapon, // Default to weapon
Name: "",
Value: 0,
Att: 0,
@ -52,39 +79,48 @@ func (i *Item) Validate() error {
}
// CRUD operations
func (i *Item) Delete() error {
return database.Exec("DELETE FROM items WHERE id = %d", i.ID)
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 {
store.Remove(i.ID)
return nil
}
// Insert with ID assignment
func (i *Item) Insert() error {
id, err := database.Insert("items", i, "ID")
id, err := store.Create(i)
if err != nil {
return err
}
i.ID = int(id)
i.ID = id
return nil
}
// Query functions
func Find(id int) (*Item, error) {
var item Item
err := database.Get(&item, "SELECT * FROM items WHERE id = %d", id)
if err != nil {
item, exists := store.Find(id)
if !exists {
return nil, fmt.Errorf("item with ID %d not found", id)
}
return &item, nil
return item, nil
}
func All() ([]*Item, error) {
var items []*Item
err := database.Select(&items, "SELECT * FROM items ORDER BY id ASC")
return items, err
return store.AllSorted("allByID"), nil
}
func ByType(itemType int) ([]*Item, error) {
var items []*Item
err := database.Select(&items, "SELECT * FROM items WHERE type = %d ORDER BY id ASC", itemType)
return items, err
return store.GroupByIndex("byType", itemType), nil
}
// Helper methods
@ -120,3 +156,14 @@ func (i *Item) HasSpecial() bool {
func (i *Item) IsEquippable() bool {
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 (
"fmt"
"dk/internal/database"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
// Monster represents a monster in the game
type Monster struct {
ID int
Name string
MaxHP int
MaxDmg int
Armor int
Level int
MaxExp int
MaxGold int
Immune int
ID int `json:"id"`
Name string `json:"name" db:"required"`
MaxHP int `json:"max_hp"`
MaxDmg int `json:"max_dmg"`
Armor int `json:"armor"`
Level int `json:"level" db:"index"`
MaxExp int `json:"max_exp"`
MaxGold int `json:"max_gold"`
Immune int `json:"immune" db:"index"`
}
// Immunity constants
@ -26,7 +26,41 @@ const (
ImmuneSleep = 2
)
// New creates a new Monster with sensible defaults
// Global store
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 {
return &Monster{
Name: "",
@ -58,51 +92,61 @@ func (m *Monster) Validate() error {
}
// CRUD operations
func (m *Monster) Delete() error {
return database.Exec("DELETE FROM monsters WHERE id = %d", m.ID)
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 {
store.Remove(m.ID)
return nil
}
// Insert with ID assignment
func (m *Monster) Insert() error {
id, err := database.Insert("monsters", m, "ID")
id, err := store.Create(m)
if err != nil {
return err
}
m.ID = int(id)
m.ID = id
return nil
}
// Query functions
func Find(id int) (*Monster, error) {
var monster Monster
err := database.Get(&monster, "SELECT * FROM monsters WHERE id = %d", id)
if err != nil {
monster, exists := store.Find(id)
if !exists {
return nil, fmt.Errorf("monster with ID %d not found", id)
}
return &monster, nil
return monster, nil
}
func All() ([]*Monster, error) {
var monsters []*Monster
err := database.Select(&monsters, "SELECT * FROM monsters ORDER BY level ASC, id ASC")
return monsters, err
return store.AllSorted("allByLevel"), nil
}
func ByLevel(level int) ([]*Monster, error) {
var monsters []*Monster
err := database.Select(&monsters, "SELECT * FROM monsters WHERE level = %d ORDER BY id ASC", level)
return monsters, err
return store.GroupByIndex("byLevel", level), nil
}
func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) {
var monsters []*Monster
err := database.Select(&monsters, "SELECT * FROM monsters WHERE level >= %d AND level <= %d ORDER BY level ASC, id ASC", minLevel, maxLevel)
return monsters, err
var result []*Monster
for level := minLevel; level <= maxLevel; level++ {
monsters := store.GroupByIndex("byLevel", level)
result = append(result, monsters...)
}
return result, nil
}
func ByImmunity(immunityType int) ([]*Monster, error) {
var monsters []*Monster
err := database.Select(&monsters, "SELECT * FROM monsters WHERE immune = %d ORDER BY level ASC, id ASC", immunityType)
return monsters, err
return store.GroupByIndex("byImmunity", immunityType), nil
}
// Helper methods
@ -151,3 +195,14 @@ func (m *Monster) GoldPerHP() float64 {
}
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,23 +5,53 @@ import (
"strings"
"time"
"dk/internal/database"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
// News represents a news post in the game
type News struct {
ID int
Author int
Posted int64
Content string
ID int `json:"id"`
Author int `json:"author" db:"index"`
Posted int64 `json:"posted"`
Content string `json:"content" db:"required"`
}
// New creates a new News with sensible defaults
// Global store
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 {
return &News{
Author: 0,
Posted: time.Now().Unix(),
Content: "",
Author: 0, // No author by default
Posted: time.Now().Unix(), // Current time
Content: "", // Empty content
}
}
@ -37,64 +67,75 @@ func (n *News) Validate() error {
}
// CRUD operations
func (n *News) Delete() error {
return database.Exec("DELETE FROM news WHERE id = %d", n.ID)
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 {
store.Remove(n.ID)
return nil
}
// Insert with ID assignment
func (n *News) Insert() error {
id, err := database.Insert("news", n, "ID")
id, err := store.Create(n)
if err != nil {
return err
}
n.ID = int(id)
n.ID = id
return nil
}
// Query functions
func Find(id int) (*News, error) {
var news News
err := database.Get(&news, "SELECT * FROM news WHERE id = %d", id)
if err != nil {
news, exists := store.Find(id)
if !exists {
return nil, fmt.Errorf("news with ID %d not found", id)
}
return &news, nil
return news, nil
}
func All() ([]*News, error) {
var news []*News
err := database.Select(&news, "SELECT * FROM news ORDER BY posted DESC, id DESC")
return news, err
return store.AllSorted("allByPosted"), nil
}
func ByAuthor(authorID int) ([]*News, error) {
var news []*News
err := database.Select(&news, "SELECT * FROM news WHERE author = %d ORDER BY posted DESC, id DESC", authorID)
return news, err
return store.GroupByIndex("byAuthor", authorID), nil
}
func Recent(limit int) ([]*News, error) {
var news []*News
err := database.Select(&news, "SELECT * FROM news ORDER BY posted DESC, id DESC LIMIT %d", limit)
return news, err
all := store.AllSorted("allByPosted")
if limit > len(all) {
limit = len(all)
}
return all[:limit], nil
}
func Since(since int64) ([]*News, error) {
var news []*News
err := database.Select(&news, "SELECT * FROM news WHERE posted >= %d ORDER BY posted DESC, id DESC", since)
return news, err
return store.FilterByIndex("allByPosted", func(n *News) bool {
return n.Posted >= since
}), nil
}
func Between(start, end int64) ([]*News, error) {
var news []*News
err := database.Select(&news, "SELECT * FROM news WHERE posted >= %d AND posted <= %d ORDER BY posted DESC, id DESC", start, end)
return news, err
return store.FilterByIndex("allByPosted", func(n *News) bool {
return n.Posted >= start && n.Posted <= end
}), nil
}
func Search(term string) ([]*News, error) {
var news []*News
searchTerm := "%" + term + "%"
err := database.Select(&news, "SELECT * FROM news WHERE content LIKE %s ORDER BY posted DESC, id DESC", searchTerm)
return news, err
lowerTerm := strings.ToLower(term)
return store.FilterByIndex("allByPosted", func(n *News) bool {
return strings.Contains(strings.ToLower(n.Content), lowerTerm)
}), nil
}
// Helper methods
@ -172,3 +213,14 @@ func (n *News) Contains(term string) bool {
func (n *News) IsEmpty() bool {
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,17 +2,18 @@ package spells
import (
"fmt"
"strings"
"dk/internal/database"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
// Spell represents a spell in the game
type Spell struct {
ID int
Name string
MP int
Attribute int
Type int
ID int `json:"id"`
Name string `json:"name" db:"required,unique"`
MP int `json:"mp" db:"index"`
Attribute int `json:"attribute"`
Type int `json:"type" db:"index"`
}
// SpellType constants for spell types
@ -24,13 +25,54 @@ const (
TypeDefenseBoost = 5
)
// New creates a new Spell with sensible defaults
// Global store
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 {
return &Spell{
Name: "",
MP: 5,
Attribute: 10,
Type: TypeHealing,
MP: 5, // Default MP cost
Attribute: 10, // Default attribute value
Type: TypeHealing, // Default to healing spell
}
}
@ -52,60 +94,68 @@ func (s *Spell) Validate() error {
}
// CRUD operations
func (s *Spell) Delete() error {
return database.Exec("DELETE FROM spells WHERE id = %d", s.ID)
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 {
store.Remove(s.ID)
return nil
}
// Insert with ID assignment
func (s *Spell) Insert() error {
id, err := database.Insert("spells", s, "ID")
id, err := store.Create(s)
if err != nil {
return err
}
s.ID = int(id)
s.ID = id
return nil
}
// Query functions
func Find(id int) (*Spell, error) {
var spell Spell
err := database.Get(&spell, "SELECT * FROM spells WHERE id = %d", id)
if err != nil {
spell, exists := store.Find(id)
if !exists {
return nil, fmt.Errorf("spell with ID %d not found", id)
}
return &spell, nil
return spell, nil
}
func All() ([]*Spell, error) {
var spells []*Spell
err := database.Select(&spells, "SELECT * FROM spells ORDER BY type ASC, mp ASC, id ASC")
return spells, err
return store.AllSorted("allByTypeMP"), nil
}
func ByType(spellType int) ([]*Spell, error) {
var spells []*Spell
err := database.Select(&spells, "SELECT * FROM spells WHERE type = %d ORDER BY mp ASC, id ASC", spellType)
return spells, err
return store.GroupByIndex("byType", spellType), nil
}
func ByMaxMP(maxMP int) ([]*Spell, error) {
var spells []*Spell
err := database.Select(&spells, "SELECT * FROM spells WHERE mp <= %d ORDER BY type ASC, mp ASC, id ASC", maxMP)
return spells, err
return store.FilterByIndex("allByTypeMP", func(s *Spell) bool {
return s.MP <= maxMP
}), nil
}
func ByTypeAndMaxMP(spellType, maxMP int) ([]*Spell, error) {
var spells []*Spell
err := database.Select(&spells, "SELECT * FROM spells WHERE type = %d AND mp <= %d ORDER BY mp ASC, id ASC", spellType, maxMP)
return spells, err
return store.FilterByIndex("allByTypeMP", func(s *Spell) bool {
return s.Type == spellType && s.MP <= maxMP
}), nil
}
func ByName(name string) (*Spell, error) {
var spell Spell
err := database.Get(&spell, "SELECT * FROM spells WHERE name = %s COLLATE NOCASE", name)
if err != nil {
spell, exists := store.LookupByIndex("byName", strings.ToLower(name))
if !exists {
return nil, fmt.Errorf("spell with name '%s' not found", name)
}
return &spell, nil
return spell, nil
}
// Helper methods
@ -164,3 +214,14 @@ func (s *Spell) IsOffensive() bool {
func (s *Spell) IsSupport() bool {
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,10 +9,21 @@ import (
"path/filepath"
"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/routes"
"dk/internal/template"
nigiri "git.sharkk.net/Sharkk/Nigiri"
sushi "git.sharkk.net/Sharkk/Sushi"
"git.sharkk.net/Sharkk/Sushi/auth"
"git.sharkk.net/Sharkk/Sushi/csrf"
@ -57,6 +68,11 @@ func start(port string) error {
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()
sushi.InitSessions(filepath.Join(cwd, "data/_sessions.json"))
@ -92,6 +108,11 @@ func start(port string) error {
<-c
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...")
sushi.SaveSessions()
@ -99,6 +120,33 @@ func start(port string) error {
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 {
return users.GetByID(userID)
}