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 ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"time" "time"
"dk/internal/database" nigiri "git.sharkk.net/Sharkk/Nigiri"
) )
// 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 ID int `json:"id"`
Posted int64 Posted int64 `json:"posted"`
Author int Author string `json:"author" db:"index"`
Babble string Babble string `json:"babble"`
}
// 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
@ -30,8 +22,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 b.Author <= 0 { if strings.TrimSpace(b.Author) == "" {
return fmt.Errorf("babble Author must be a valid user ID") return fmt.Errorf("babble Author cannot be empty")
} }
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")
@ -39,71 +31,139 @@ func (b *Babble) Validate() error {
return nil return nil
} }
func (b *Babble) Delete() error { // Global store with singleton pattern
return database.Exec("DELETE FROM babble WHERE id = %d", b.ID) 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 { // GetStore returns the babble store
id, err := database.Insert("babble", b, "ID") func GetStore() *nigiri.BaseStore[Babble] {
if err != nil { if store == nil {
return err 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 return nil
} }
func Find(id int) (*Babble, error) { // Insert with ID assignment
var babble Babble func (b *Babble) Insert() error {
err := database.Get(&babble, "SELECT * FROM babble WHERE id = %d", id) id, err := store.Create(b)
if err != nil { 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 nil, fmt.Errorf("babble with ID %d not found", id)
} }
return &babble, nil return babble, nil
} }
func All() ([]*Babble, error) { func All() ([]*Babble, error) {
var babbles []*Babble return store.AllSorted("allByPosted"), nil
err := database.Select(&babbles, "SELECT * FROM babble ORDER BY posted DESC, id DESC")
return babbles, err
} }
func ByAuthor(authorID int) ([]*Babble, error) { func ByAuthor(author string) ([]*Babble, error) {
var babbles []*Babble messages := store.GroupByIndex("byAuthor", strings.ToLower(author))
err := database.Select(&babbles, "SELECT * FROM babble WHERE author = %d ORDER BY posted DESC, id DESC", authorID)
return babbles, err // 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) { func Recent(limit int) ([]*Babble, error) {
var babbles []*Babble all := store.AllSorted("allByPosted")
err := database.Select(&babbles, "SELECT * FROM babble ORDER BY posted DESC, id DESC LIMIT %d", limit) if limit > len(all) {
return babbles, err limit = len(all)
}
return all[:limit], nil
} }
func Since(since int64) ([]*Babble, error) { func Since(since int64) ([]*Babble, error) {
var babbles []*Babble return store.FilterByIndex("allByPosted", func(b *Babble) bool {
err := database.Select(&babbles, "SELECT * FROM babble WHERE posted >= %d ORDER BY posted DESC, id DESC", since) return b.Posted >= since
return babbles, err }), nil
} }
func Between(start, end int64) ([]*Babble, error) { func Between(start, end int64) ([]*Babble, error) {
var babbles []*Babble return store.FilterByIndex("allByPosted", func(b *Babble) bool {
err := database.Select(&babbles, "SELECT * FROM babble WHERE posted >= %d AND posted <= %d ORDER BY posted DESC, id DESC", start, end) return b.Posted >= start && b.Posted <= end
return babbles, err }), nil
} }
func Search(term string) ([]*Babble, error) { func Search(term string) ([]*Babble, error) {
var babbles []*Babble lowerTerm := strings.ToLower(term)
searchTerm := "%" + term + "%" return store.FilterByIndex("allByPosted", func(b *Babble) bool {
err := database.Select(&babbles, "SELECT * FROM babble WHERE babble LIKE %s ORDER BY posted DESC, id DESC", searchTerm) return strings.Contains(strings.ToLower(b.Babble), lowerTerm)
return babbles, err }), nil
} }
func RecentByAuthor(authorID int, limit int) ([]*Babble, error) { func RecentByAuthor(author string, limit int) ([]*Babble, error) {
var babbles []*Babble messages, err := ByAuthor(author)
err := database.Select(&babbles, "SELECT * FROM babble ORDER BY posted DESC, id DESC LIMIT %d", authorID, limit) if err != nil {
return babbles, err return nil, 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)
} }
@ -120,8 +180,8 @@ func (b *Babble) Age() time.Duration {
return time.Since(b.PostedTime()) return time.Since(b.PostedTime())
} }
func (b *Babble) IsAuthor(userID int) bool { func (b *Babble) IsAuthor(username string) bool {
return b.Author == userID return strings.EqualFold(b.Author, username)
} }
func (b *Babble) Preview(maxLength int) string { func (b *Babble) Preview(maxLength int) string {
@ -205,3 +265,14 @@ 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

@ -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 (
global *Control store *nigiri.BaseStore[Control]
mu sync.RWMutex global *Control
filename string mu sync.RWMutex
) )
// Control represents the game control settings // Control represents the game control settings
@ -24,72 +24,39 @@ type Control struct {
Class3Name string `json:"class_3_name"` Class3Name string `json:"class_3_name"`
} }
// Init loads control settings from the specified JSON file // Init sets up the Nigiri store for control settings
func Init(jsonFile string) error { func Init(collection *nigiri.Collection) {
mu.Lock() store = nigiri.NewBaseStore[Control]()
defer mu.Unlock()
filename = jsonFile // Load or create the singleton control instance
all := store.GetAll()
// Try to load from file if len(all) == 0 {
if data, err := os.ReadFile(filename); err == nil { // Create default control settings
var ctrl Control global = New()
if err := json.Unmarshal(data, &ctrl); err != nil { global.ID = 1
return fmt.Errorf("failed to parse JSON: %w", err) 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 // Apply defaults for any missing fields
defaults := New() defaults := New()
if ctrl.WorldSize == 0 { if global.WorldSize == 0 {
ctrl.WorldSize = defaults.WorldSize global.WorldSize = defaults.WorldSize
} }
if ctrl.Class1Name == "" { if global.Class1Name == "" {
ctrl.Class1Name = defaults.Class1Name global.Class1Name = defaults.Class1Name
} }
if ctrl.Class2Name == "" { if global.Class2Name == "" {
ctrl.Class2Name = defaults.Class2Name global.Class2Name = defaults.Class2Name
} }
if ctrl.Class3Name == "" { if global.Class3Name == "" {
ctrl.Class3Name = defaults.Class3Name global.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
@ -110,12 +77,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 Init first") panic("control not initialized - call Initialize first")
} }
return global 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 { func Set(control *Control) error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@ -125,11 +92,15 @@ func Set(control *Control) error {
return err return err
} }
if err := store.Update(1, control); err != nil {
return err
}
global = control 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 { func Update(updater func(*Control)) error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@ -142,20 +113,7 @@ func Update(updater func(*Control)) error {
return err return err
} }
global = &updated if err := store.Update(1, &updated); err != nil {
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
} }
@ -276,3 +234,14 @@ 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

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

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

View File

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