exports.up = async function (knex) { // System configuration with hot-reloading support await knex.schema.createTable('system_config', (table) => { table.increments('id').primary(); table.string('config_key', 100).unique().notNullable(); table.jsonb('config_value').notNullable(); table.string('config_type', 20).notNullable().checkIn(['string', 'number', 'boolean', 'json', 'array']); table.text('description'); table.boolean('requires_restart').defaultTo(false); table.boolean('is_public').defaultTo(false); // Can be exposed to client table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); table.integer('updated_by'); // Will reference admin_users(id) after table creation }); // Game tick system with user grouping await knex.schema.createTable('game_tick_config', (table) => { table.increments('id').primary(); table.integer('tick_interval_ms').notNullable().defaultTo(60000); table.integer('user_groups_count').notNullable().defaultTo(10); table.integer('max_retry_attempts').notNullable().defaultTo(5); table.integer('bonus_tick_threshold').notNullable().defaultTo(3); table.boolean('is_active').defaultTo(true); table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); }); await knex.schema.createTable('game_tick_log', (table) => { table.bigIncrements('id').primary(); table.bigInteger('tick_number').notNullable(); table.integer('user_group').notNullable(); table.timestamp('started_at').notNullable(); table.timestamp('completed_at'); table.string('status', 20).notNullable().checkIn(['running', 'completed', 'failed', 'retrying']); table.integer('retry_count').defaultTo(0); table.text('error_message'); table.integer('processed_players').defaultTo(0); table.jsonb('performance_metrics'); table.timestamp('created_at').defaultTo(knex.fn.now()); table.index(['tick_number']); table.index(['user_group']); table.index(['status']); }); // Event system configuration await knex.schema.createTable('event_types', (table) => { table.increments('id').primary(); table.string('name', 100).unique().notNullable(); table.text('description'); table.string('trigger_type', 20).notNullable().checkIn(['admin', 'player', 'system', 'mixed']); table.boolean('is_active').defaultTo(true); table.jsonb('config_schema'); // JSON schema for event configuration table.timestamp('created_at').defaultTo(knex.fn.now()); }); await knex.schema.createTable('event_instances', (table) => { table.bigIncrements('id').primary(); table.integer('event_type_id').notNullable().references('id').inTable('event_types'); table.string('name', 200).notNullable(); table.text('description'); table.jsonb('config').notNullable(); table.timestamp('start_time'); table.timestamp('end_time'); table.string('status', 20).notNullable().checkIn(['scheduled', 'active', 'completed', 'cancelled']); table.integer('created_by'); // Will reference admin_users(id) after table creation table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); table.index(['event_type_id']); table.index(['status']); table.index(['start_time']); }); // Plugin system for extensibility await knex.schema.createTable('plugins', (table) => { table.increments('id').primary(); table.string('name', 100).unique().notNullable(); table.string('version', 20).notNullable(); table.text('description'); table.string('plugin_type', 50).notNullable(); // 'combat', 'event', 'resource', etc. table.boolean('is_active').defaultTo(false); table.jsonb('config'); table.jsonb('dependencies'); // Array of required plugins table.jsonb('hooks'); // Available hook points table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); }); // Insert initial system configuration await knex('system_config').insert([ { config_key: 'game_tick_interval_ms', config_value: JSON.stringify(60000), config_type: 'number', description: 'Game tick interval in milliseconds', is_public: false, }, { config_key: 'max_user_groups', config_value: JSON.stringify(10), config_type: 'number', description: 'Maximum number of user groups for tick processing', is_public: false, }, { config_key: 'max_retry_attempts', config_value: JSON.stringify(5), config_type: 'number', description: 'Maximum retry attempts for failed ticks', is_public: false, }, { config_key: 'data_retention_days', config_value: JSON.stringify(30), config_type: 'number', description: 'Default data retention period in days', is_public: false, }, { config_key: 'max_colonies_per_player', config_value: JSON.stringify(10), config_type: 'number', description: 'Maximum colonies a player can own', is_public: true, }, { config_key: 'starting_resources', config_value: JSON.stringify({ scrap: 1000, energy: 500, data_cores: 0, rare_elements: 0 }), config_type: 'json', description: 'Starting resources for new players', is_public: false, }, { config_key: 'websocket_ping_interval', config_value: JSON.stringify(30000), config_type: 'number', description: 'WebSocket ping interval in milliseconds', is_public: false, }, ]); // Insert initial game tick configuration await knex('game_tick_config').insert({ tick_interval_ms: 60000, user_groups_count: 10, max_retry_attempts: 5, bonus_tick_threshold: 3, }); // Insert initial event types await knex('event_types').insert([ { name: 'galaxy_crisis', description: 'Major galaxy-wide crisis events', trigger_type: 'admin', config_schema: JSON.stringify({ duration_hours: { type: 'number', min: 1, max: 168 } }), }, { name: 'discovery_event', description: 'Random discovery events triggered by exploration', trigger_type: 'player', config_schema: JSON.stringify({ discovery_type: { type: 'string', enum: ['artifact', 'technology', 'resource'] } }), }, { name: 'faction_war', description: 'Large-scale conflicts between factions', trigger_type: 'mixed', config_schema: JSON.stringify({ participating_factions: { type: 'array', items: { type: 'number' } } }), }, ]); // Insert initial plugin for basic combat await knex('plugins').insert({ name: 'basic_combat', version: '1.0.0', description: 'Basic instant combat resolution system', plugin_type: 'combat', is_active: true, config: JSON.stringify({ damage_variance: 0.1, experience_gain: 1.0 }), hooks: JSON.stringify(['pre_combat', 'post_combat', 'damage_calculation']), }); }; exports.down = async function (knex) { await knex.schema.dropTableIfExists('plugins'); await knex.schema.dropTableIfExists('event_instances'); await knex.schema.dropTableIfExists('event_types'); await knex.schema.dropTableIfExists('game_tick_log'); await knex.schema.dropTableIfExists('game_tick_config'); await knex.schema.dropTableIfExists('system_config'); };