Compare commits

...

2 Commits

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

350
database.sql Normal file
View File

@ -0,0 +1,350 @@
DROP TABLE IF EXISTS babble;
CREATE TABLE babble (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`posted` INTEGER NOT NULL DEFAULT (unixepoch()),
`author` INTEGER NOT NULL,
`babble` TEXT NOT NULL
);
DROP TABLE IF EXISTS drops;
CREATE TABLE drops (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL,
`level` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL DEFAULT 0,
`att` TEXT NOT NULL DEFAULT '',
);
INSERT INTO drops VALUES
(1, 'Life Pebble', 1, 1, 'maxhp,10'),
(2, 'Life Stone', 10, 1, 'maxhp,25'),
(3, 'Life Rock', 25, 1, 'maxhp,50'),
(4, 'Magic Pebble', 1, 1, 'maxmp,10'),
(5, 'Magic Stone', 10, 1, 'maxmp,25'),
(6, 'Magic Rock', 25, 1, 'maxmp,50'),
(7, 'Dragon''s Scale', 10, 1, 'defensepower,25'),
(8, 'Dragon''s Plate', 30, 1, 'defensepower,50'),
(9, 'Dragon''s Claw', 10, 1, 'attackpower,25'),
(10, 'Dragon''s Tooth', 30, 1, 'attackpower,50'),
(11, 'Dragon''s Tear', 35, 1, 'strength,50'),
(12, 'Dragon''s Wing', 35, 1, 'dexterity,50'),
(13, 'Demon''s Sin', 35, 1, 'maxhp,-50,strength,50'),
(14, 'Demon''s Fall', 35, 1, 'maxmp,-50,strength,50'),
(15, 'Demon''s Lie', 45, 1, 'maxhp,-100,strength,100'),
(16, 'Demon''s Hate', 45, 1, 'maxmp,-100,strength,100'),
(17, 'Angel''s Joy', 25, 1, 'maxhp,25,strength,25'),
(18, 'Angel''s Rise', 30, 1, 'maxhp,50,strength,50'),
(19, 'Angel''s Truth', 35, 1, 'maxhp,75,strength,75'),
(20, 'Angel''s Love', 40, 1, 'maxhp,100,strength,100'),
(21, 'Seraph''s Joy', 25, 1, 'maxmp,25,dexterity,25'),
(22, 'Seraph''s Rise', 30, 1, 'maxmp,50,dexterity,50'),
(23, 'Seraph''s Truth', 35, 1, 'maxmp,75,dexterity,75'),
(24, 'Seraph''s Love', 40, 1, 'maxmp,100,dexterity,100'),
(25, 'Ruby', 50, 1, 'maxhp,150'),
(26, 'Pearl', 50, 1, 'maxmp,150'),
(27, 'Emerald', 50, 1, 'strength,150'),
(28, 'Topaz', 50, 1, 'dexterity,150'),
(29, 'Obsidian', 50, 1, 'attackpower,150'),
(30, 'Diamond', 50, 1, 'defensepower,150'),
(31, 'Memory Drop', 5, 1, 'expbonus,10'),
(32, 'Fortune Drop', 5, 1, 'goldbonus,10');
DROP TABLE IF EXISTS forum;
CREATE TABLE forum (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`posted` INTEGER NOT NULL DEFAULT (unixepoch()),
`last_post` INTEGER NOT NULL DEFAULT (unixepoch()),
`author` INTEGER NOT NULL,
`parent` INTEGER NOT NULL DEFAULT 0,
`replies` INTEGER NOT NULL DEFAULT 0,
`title` TEXT NOT NULL DEFAULT '',
`content` TEXT NOT NULL
);
DROP TABLE IF EXISTS items;
CREATE TABLE items (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`type` INTEGER NOT NULL DEFAULT 1,
`name` TEXT NOT NULL,
`value` INTEGER NOT NULL DEFAULT 0,
`att` INTEGER NOT NULL DEFAULT 0,
`special` TEXT NOT NULL DEFAULT ''
);
INSERT INTO items VALUES
(1, 1, 'Stick', 10, 2, ''),
(2, 1, 'Branch', 30, 4, ''),
(3, 1, 'Club', 40, 5, ''),
(4, 1, 'Dagger', 90, 8, ''),
(5, 1, 'Hatchet', 150, 12, ''),
(6, 1, 'Axe', 200, 16, ''),
(7, 1, 'Brand', 300, 25, ''),
(8, 1, 'Poleaxe', 500, 35, ''),
(9, 1, 'Broadsword', 800, 45, ''),
(10, 1, 'Battle Axe', 1200, 50, ''),
(11, 1, 'Claymore', 2000, 60, ''),
(12, 1, 'Dark Axe', 3000, 100, 'expbonus,-5'),
(13, 1, 'Dark Sword', 4500, 125, 'expbonus,-10'),
(14, 1, 'Bright Sword', 6000, 100, 'expbonus,10'),
(15, 1, 'Magic Sword', 10000, 150, 'maxmp,50'),
(16, 1, 'Destiny Blade', 50000, 250, 'strength,50'),
(17, 2, 'Skivvies', 25, 2, 'goldbonus,10'),
(18, 2, 'Clothes', 50, 5, ''),
(19, 2, 'Leather Armor', 75, 10, ''),
(20, 2, 'Hard Leather Armor', 150, 25, ''),
(21, 2, 'Chain Mail', 300, 30, ''),
(22, 2, 'Bronze Plate', 900, 50, ''),
(23, 2, 'Iron Plate', 2000, 100, ''),
(24, 2, 'Magic Armor', 4000, 125, 'maxmp,50'),
(25, 2, 'Dark Armor', 5000, 150, 'expbonus,-10'),
(26, 2, 'Bright Armor', 10000, 175, 'expbonus,10'),
(27, 2, 'Destiny Raiment', 50000, 200, 'dexterity,50'),
(28, 3, 'Reed Shield', 50, 2, ''),
(29, 3, 'Buckler', 100, 4, ''),
(30, 3, 'Small Shield', 500, 10, ''),
(31, 3, 'Large Shield', 2500, 30, ''),
(32, 3, 'Silver Shield', 10000, 60, ''),
(33, 3, 'Destiny Aegis', 25000, 100, 'maxhp,50');
DROP TABLE IF EXISTS classes;
CREATE TABLE classes (
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
'name' TEXT NOT NULL,
'lore' TEXT NOT NULL,
'exp_rate' INTEGER NOT NULL DEFAULT 3,
'base_hp' INTEGER NOT NULL DEFAULT 15,
'base_mp' INTEGER NOT NULL DEFAULT 10,
'base_str' INTEGER NOT NULL DEFAULT 1,
'base_dex' INTEGER NOT NULL DEFAULT 1,
'hp_rate' INTEGER NOT NULL DEFAULT 2,
'mp_rate' INTEGER NOT NULL DEFAULT 2,
'str_rate' INTEGER NOT NULL DEFAULT 2,
'dex_rate' INTEGER NOT NULL DEFAULT 2,
);
INSERT INTO classes VALUES
(1, 'Adventurer', '', 3, 15, 10, 4, 4, 2, 2, 2, 2),
(2, 'Mage', '', 1, 10, 15, 1, 7, 1, 3, 1, 2),
(3, 'Warrior', '', 2, 20, 5, 7, 1, 3, 1, 3, 1),
(4, 'Paladin', '', 5, 15, 15, 5, 5, 2, 2, 2, 2);
DROP TABLE IF EXISTS monsters;
CREATE TABLE monsters (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL,
`max_hp` INTEGER NOT NULL DEFAULT 1,
`max_dmg` INTEGER NOT NULL DEFAULT 1,
`armor` INTEGER NOT NULL DEFAULT 0,
`level` INTEGER NOT NULL DEFAULT 0,
`max_exp` INTEGER NOT NULL DEFAULT 1,
`max_gold` INTEGER NOT NULL DEFAULT 1,
`immune` INTEGER NOT NULL DEFAULT 0
);
INSERT INTO monsters VALUES
(1, 'Blue Slime', 4, 3, 1, 1, 1, 1, 0),
(2, 'Red Slime', 6, 5, 1, 1, 2, 1, 0),
(3, 'Critter', 6, 5, 2, 1, 4, 2, 0),
(4, 'Creature', 10, 8, 2, 2, 4, 2, 0),
(5, 'Shadow', 10, 9, 3, 2, 6, 2, 1),
(6, 'Drake', 11, 10, 3, 2, 8, 3, 0),
(7, 'Shade', 12, 10, 3, 3, 10, 3, 1),
(8, 'Drakelor', 14, 12, 4, 3, 10, 3, 0),
(9, 'Silver Slime', 15, 100, 200, 30, 15, 1000, 2),
(10, 'Scamp', 16, 13, 5, 4, 15, 5, 0),
(11, 'Raven', 16, 13, 5, 4, 18, 6, 0),
(12, 'Scorpion', 18, 14, 6, 5, 20, 7, 0),
(13, 'Illusion', 20, 15, 6, 5, 20, 7, 1),
(14, 'Nightshade', 22, 16, 6, 6, 24, 8, 0),
(15, 'Drakemal', 22, 18, 7, 6, 24, 8, 0),
(16, 'Shadow Raven', 24, 18, 7, 6, 26, 9, 1),
(17, 'Ghost', 24, 20, 8, 6, 28, 9, 0),
(18, 'Frost Raven', 26, 20, 8, 7, 30, 10, 0),
(19, 'Rogue Scorpion', 28, 22, 9, 7, 32, 11, 0),
(20, 'Ghoul', 29, 24, 9, 7, 34, 11, 0),
(21, 'Magician', 30, 24, 10, 8, 36, 12, 0),
(22, 'Rogue', 30, 25, 12, 8, 40, 13, 0),
(23, 'Drakefin', 32, 26, 12, 8, 40, 13, 0),
(24, 'Shimmer', 32, 26, 14, 8, 45, 15, 1),
(25, 'Fire Raven', 34, 28, 14, 9, 45, 15, 0),
(26, 'Dybbuk', 34, 28, 14, 9, 50, 17, 0),
(27, 'Knave', 36, 30, 15, 9, 52, 17, 0),
(28, 'Goblin', 36, 30, 15, 10, 54, 18, 0),
(29, 'Skeleton', 38, 30, 18, 10, 58, 19, 0),
(30, 'Dark Slime', 38, 32, 18, 10, 62, 21, 0),
(31, 'Silver Scorpion', 30, 160, 350, 40, 63, 2000, 2),
(32, 'Mirage', 40, 32, 20, 11, 64, 21, 1),
(33, 'Sorceror', 41, 33, 22, 11, 68, 23, 0),
(34, 'Imp', 42, 34, 22, 12, 70, 23, 0),
(35, 'Nymph', 43, 35, 22, 12, 70, 23, 0),
(36, 'Scoundrel', 43, 35, 22, 12, 75, 25, 0),
(37, 'Megaskeleton', 44, 36, 24, 13, 78, 26, 0),
(38, 'Grey Wolf', 44, 36, 24, 13, 82, 27, 0),
(39, 'Phantom', 46, 38, 24, 14, 85, 28, 1),
(40, 'Specter', 46, 38, 24, 14, 90, 30, 0),
(41, 'Dark Scorpion', 48, 40, 26, 15, 95, 32, 1),
(42, 'Warlock', 48, 40, 26, 15, 100, 33, 1),
(43, 'Orc', 49, 42, 28, 15, 104, 35, 0),
(44, 'Sylph', 49, 42, 28, 15, 106, 35, 0),
(45, 'Wraith', 50, 45, 30, 16, 108, 36, 0),
(46, 'Hellion', 50, 45, 30, 16, 110, 37, 0),
(47, 'Bandit', 52, 45, 30, 16, 114, 38, 0),
(48, 'Ultraskeleton', 52, 46, 32, 16, 116, 39, 0),
(49, 'Dark Wolf', 54, 47, 36, 17, 120, 40, 1),
(50, 'Troll', 56, 48, 36, 17, 120, 40, 0),
(51, 'Werewolf', 56, 48, 38, 17, 124, 41, 0),
(52, 'Hellcat', 58, 50, 38, 18, 128, 43, 0),
(53, 'Spirit', 58, 50, 38, 18, 132, 44, 0),
(54, 'Nisse', 60, 52, 40, 19, 132, 44, 0),
(55, 'Dawk', 60, 54, 40, 19, 136, 45, 0),
(56, 'Figment', 64, 55, 42, 19, 140, 47, 1),
(57, 'Hellhound', 66, 56, 44, 20, 140, 47, 0),
(58, 'Wizard', 66, 56, 44, 20, 144, 48, 0),
(59, 'Uruk', 68, 58, 44, 20, 146, 49, 0),
(60, 'Siren', 68, 400, 800, 50, 10000, 50, 2),
(61, 'Megawraith', 70, 60, 46, 21, 155, 52, 0),
(62, 'Dawkin', 70, 60, 46, 21, 155, 52, 0),
(63, 'Grey Bear', 70, 62, 48, 21, 160, 53, 0),
(64, 'Haunt', 72, 62, 48, 22, 160, 53, 0),
(65, 'Hellbeast', 74, 64, 50, 22, 165, 55, 0),
(66, 'Fear', 76, 66, 52, 23, 165, 55, 0),
(67, 'Beast', 76, 66, 52, 23, 170, 57, 0),
(68, 'Ogre', 78, 68, 54, 23, 170, 57, 0),
(69, 'Dark Bear', 80, 70, 56, 24, 175, 58, 1),
(70, 'Fire', 80, 72, 56, 24, 175, 58, 0),
(71, 'Polgergeist', 84, 74, 58, 25, 180, 60, 0),
(72, 'Fright', 86, 76, 58, 25, 180, 60, 0),
(73, 'Lycan', 88, 78, 60, 25, 185, 62, 0),
(74, 'Terra Elemental', 88, 80, 62, 25, 185, 62, 1),
(75, 'Necromancer', 90, 80, 62, 26, 190, 63, 0),
(76, 'Ultrawraith', 90, 82, 64, 26, 190, 63, 0),
(77, 'Dawkor', 92, 82, 64, 26, 195, 65, 0),
(78, 'Werebear', 92, 84, 65, 26, 195, 65, 0),
(79, 'Brute', 94, 84, 65, 27, 200, 67, 0),
(80, 'Large Beast', 96, 88, 66, 27, 200, 67, 0),
(81, 'Horror', 96, 88, 68, 27, 210, 70, 0),
(82, 'Flame', 100, 90, 70, 28, 210, 70, 0),
(83, 'Lycanthor', 100, 90, 70, 28, 210, 70, 0),
(84, 'Wyrm', 100, 92, 72, 28, 220, 73, 0),
(85, 'Aero Elemental', 104, 94, 74, 29, 220, 73, 1),
(86, 'Dawkare', 106, 96, 76, 29, 220, 73, 0),
(87, 'Large Brute', 108, 98, 78, 29, 230, 77, 0),
(88, 'Frost Wyrm', 110, 100, 80, 30, 230, 77, 0),
(89, 'Knight', 110, 102, 80, 30, 240, 80, 0),
(90, 'Lycanthra', 112, 104, 82, 30, 240, 80, 0),
(91, 'Terror', 115, 108, 84, 31, 250, 83, 0),
(92, 'Blaze', 118, 108, 84, 31, 250, 83, 0),
(93, 'Aqua Elemental', 120, 110, 90, 31, 260, 87, 1),
(94, 'Fire Wyrm', 120, 110, 90, 32, 260, 87, 0),
(95, 'Lesser Wyvern', 122, 110, 92, 32, 270, 90, 0),
(96, 'Doomer', 124, 112, 92, 32, 270, 90, 0),
(97, 'Armor Knight', 130, 115, 95, 33, 280, 93, 0),
(98, 'Wyvern', 134, 120, 95, 33, 290, 97, 0),
(99, 'Nightmare', 138, 125, 100, 33, 300, 100, 0),
(100, 'Fira Elemental', 140, 125, 100, 34, 310, 103, 1),
(101, 'Megadoomer', 140, 128, 105, 34, 320, 107, 0),
(102, 'Greater Wyvern', 145, 130, 105, 34, 335, 112, 0),
(103, 'Advocate', 148, 132, 108, 35, 350, 117, 0),
(104, 'Strong Knight', 150, 135, 110, 35, 365, 122, 0),
(105, 'Liche', 150, 135, 110, 35, 380, 127, 0),
(106, 'Ultradoomer', 155, 140, 115, 36, 395, 132, 0),
(107, 'Fanatic', 160, 140, 115, 36, 410, 137, 0),
(108, 'Green Dragon', 160, 140, 115, 36, 425, 142, 0),
(109, 'Fiend', 160, 145, 120, 37, 445, 148, 0),
(110, 'Greatest Wyvern', 162, 150, 120, 37, 465, 155, 0),
(111, 'Lesser Devil', 164, 150, 120, 37, 485, 162, 0),
(112, 'Liche Master', 168, 155, 125, 38, 505, 168, 0),
(113, 'Zealot', 168, 155, 125, 38, 530, 177, 0),
(114, 'Serafiend', 170, 155, 125, 38, 555, 185, 0),
(115, 'Pale Knight', 175, 160, 130, 39, 580, 193, 0),
(116, 'Blue Dragon', 180, 160, 130, 39, 605, 202, 0),
(117, 'Obsessive', 180, 160, 135, 40, 630, 210, 0),
(118, 'Devil', 184, 164, 135, 40, 666, 222, 0),
(119, 'Liche Prince', 190, 168, 138, 40, 660, 220, 0),
(120, 'Cherufiend', 195, 170, 140, 41, 690, 230, 0),
(121, 'Red Dragon', 200, 180, 145, 41, 720, 240, 0),
(122, 'Greater Devil', 200, 180, 145, 41, 750, 250, 0),
(123, 'Renegade', 205, 185, 150, 42, 780, 260, 0),
(124, 'Archfiend', 210, 190, 150, 42, 810, 270, 0),
(125, 'Liche Lord', 210, 190, 155, 42, 850, 283, 0),
(126, 'Greatest Devil', 215, 195, 160, 43, 890, 297, 0),
(127, 'Dark Knight', 220, 200, 160, 43, 930, 310, 0),
(128, 'Giant', 220, 200, 165, 43, 970, 323, 0),
(129, 'Shadow Dragon', 225, 200, 170, 44, 1010, 337, 0),
(130, 'Liche King', 225, 205, 170, 44, 1050, 350, 0),
(131, 'Incubus', 230, 205, 175, 44, 1100, 367, 1),
(132, 'Traitor', 230, 205, 175, 45, 1150, 383, 0),
(133, 'Demon', 240, 210, 180, 45, 1200, 400, 0),
(134, 'Dark Dragon', 245, 215, 180, 45, 1250, 417, 1),
(135, 'Insurgent', 250, 220, 190, 46, 1300, 433, 0),
(136, 'Leviathan', 255, 225, 190, 46, 1350, 450, 0),
(137, 'Grey Daemon', 260, 230, 190, 46, 1400, 467, 0),
(138, 'Succubus', 265, 240, 200, 47, 1460, 487, 1),
(139, 'Demon Prince', 270, 240, 200, 47, 1520, 507, 0),
(140, 'Black Dragon', 275, 250, 205, 47, 1580, 527, 1),
(141, 'Nihilist', 280, 250, 205, 47, 1640, 547, 0),
(142, 'Behemoth', 285, 260, 210, 48, 1700, 567, 0),
(143, 'Demagogue', 290, 260, 210, 48, 1760, 587, 0),
(144, 'Demon Lord', 300, 270, 220, 48, 1820, 607, 0),
(145, 'Red Daemon', 310, 280, 230, 48, 1880, 627, 0),
(146, 'Colossus', 320, 300, 240, 49, 1940, 647, 0),
(147, 'Demon King', 330, 300, 250, 49, 2000, 667, 0),
(148, 'Dark Daemon', 340, 320, 260, 49, 2200, 733, 1),
(149, 'Titan', 360, 340, 270, 50, 2400, 800, 0),
(150, 'Black Daemon', 400, 400, 280, 50, 3000, 1000, 1),
(151, 'Lucifuge', 600, 600, 400, 50, 10000, 10000, 2);
DROP TABLE IF EXISTS news;
CREATE TABLE news (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`author` INTEGER NOT NULL DEFAULT 0,
`posted` INTEGER NOT NULL DEFAULT (unixepoch()),
`content` TEXT NOT NULL
);
INSERT INTO news (content) VALUES ('This is the first news post. Please use the admin control panel to add another one and make this one go away.');
DROP TABLE IF EXISTS spells;
CREATE TABLE spells (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL,
`mp` INTEGER NOT NULL DEFAULT 0,
`attribute` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL DEFAULT 0
);
INSERT INTO spells VALUES
(1, 'Heal', 5, 10, 1),
(2, 'Revive', 10, 25, 1),
(3, 'Life', 25, 50, 1),
(4, 'Breath', 50, 100, 1),
(5, 'Gaia', 75, 150, 1),
(6, 'Hurt', 5, 15, 2),
(7, 'Pain', 12, 35, 2),
(8, 'Maim', 25, 70, 2),
(9, 'Rend', 40, 100, 2),
(10, 'Chaos', 50, 130, 2),
(11, 'Sleep', 10, 5, 3),
(12, 'Dream', 30, 9, 3),
(13, 'Nightmare', 60, 13, 3),
(14, 'Craze', 10, 10, 4),
(15, 'Rage', 20, 25, 4),
(16, 'Fury', 30, 50, 4),
(17, 'Ward', 10, 10, 5),
(18, 'Fend', 20, 25, 5),
(19, 'Barrier', 30, 50, 5);
DROP TABLE IF EXISTS towns;
CREATE TABLE towns (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL,
`x` INTEGER NOT NULL DEFAULT 0,
`y` INTEGER NOT NULL DEFAULT 0,
`inn_cost` INTEGER NOT NULL DEFAULT 0,
`map_cost` INTEGER NOT NULL DEFAULT 0,
`tp_cost` INTEGER NOT NULL DEFAULT 0,
`shop_list` TEXT NOT NULL
);
INSERT INTO towns VALUES
(1, 'Midworld', 0, 0, 5, 0, 0, '1,2,3,17,18,19,28,29'),
(2, 'Roma', 30, 30, 10, 25, 5, '2,3,4,18,19,29'),
(3, 'Bris', 70, -70, 25, 50, 15, '2,3,4,5,18,19,20,29.30'),
(4, 'Kalle', -100, 100, 40, 100, 30, '5,6,8,10,12,21,22,23,29,30'),
(5, 'Narcissa', -130, -130, 60, 500, 50, '4,7,9,11,13,21,22,23,29,30,31'),
(6, 'Hambry', 170, 170, 90, 1000, 80, '10,11,12,13,14,23,24,30,31'),
(7, 'Gilead', 200, -200, 100, 3000, 110, '12,13,14,15,24,25,26,32'),
(8, 'Endworld', -250, -250, 125, 9000, 160, '16,27,33');

View File

@ -1,16 +1,16 @@
package control
import (
"encoding/json"
"fmt"
"os"
"sync"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
var (
store *nigiri.BaseStore[Control]
global *Control
mu sync.RWMutex
global *Control
mu sync.RWMutex
filename string
)
// Control represents the game control settings
@ -24,39 +24,72 @@ type Control struct {
Class3Name string `json:"class_3_name"`
}
// Init sets up the Nigiri store for control settings
func Init(collection *nigiri.Collection) {
store = nigiri.NewBaseStore[Control]()
// Init loads control settings from the specified JSON file
func Init(jsonFile string) error {
mu.Lock()
defer mu.Unlock()
// 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
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)
}
// Apply defaults for any missing fields
defaults := New()
if global.WorldSize == 0 {
global.WorldSize = defaults.WorldSize
if ctrl.WorldSize == 0 {
ctrl.WorldSize = defaults.WorldSize
}
if global.Class1Name == "" {
global.Class1Name = defaults.Class1Name
if ctrl.Class1Name == "" {
ctrl.Class1Name = defaults.Class1Name
}
if global.Class2Name == "" {
global.Class2Name = defaults.Class2Name
if ctrl.Class2Name == "" {
ctrl.Class2Name = defaults.Class2Name
}
if global.Class3Name == "" {
global.Class3Name = defaults.Class3Name
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)
}
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
@ -77,12 +110,12 @@ func Get() *Control {
mu.RLock()
defer mu.RUnlock()
if global == nil {
panic("control not initialized - call Initialize first")
panic("control not initialized - call Init first")
}
return global
}
// Set updates the global control instance (thread-safe)
// Set updates the global control instance and saves to file (thread-safe)
func Set(control *Control) error {
mu.Lock()
defer mu.Unlock()
@ -92,15 +125,11 @@ func Set(control *Control) error {
return err
}
if err := store.Update(1, control); err != nil {
return err
}
global = control
return nil
return save()
}
// Update updates specific fields of the control settings
// Update updates specific fields of the control settings and saves to file
func Update(updater func(*Control)) error {
mu.Lock()
defer mu.Unlock()
@ -113,7 +142,20 @@ func Update(updater func(*Control)) error {
return err
}
if err := store.Update(1, &updated); err != nil {
global = &updated
return save()
}
// UpdateNoSave updates specific fields without saving (useful for batch updates)
func UpdateNoSave(updater func(*Control)) error {
mu.Lock()
defer mu.Unlock()
// Create a copy to work with
updated := *global
updater(&updated)
if err := updated.Validate(); err != nil {
return err
}
@ -234,14 +276,3 @@ 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

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

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

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

View File

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

View File

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

View File

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