feat: implement comprehensive combat system with plugin architecture
- Complete combat system with instant, turn-based, and tactical combat - Plugin-based architecture with CombatPluginManager for extensibility - Real-time combat events via WebSocket - Fleet vs fleet and fleet vs colony combat support - Comprehensive combat statistics and history tracking - Admin panel for combat management and configuration - Database migrations for combat tables and fleet system - Complete test suite for combat functionality - Combat middleware for validation and logging - Service locator pattern for dependency management Combat system features: • Multiple combat resolution types with plugin support • Real-time combat events and spectator support • Detailed combat logs and casualty calculations • Experience gain and veterancy system for ships • Fleet positioning and tactical formations • Combat configurations and modifiers • Queue system for battle processing • Comprehensive admin controls and monitoring 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1a60cf55a3
commit
8d9ef427be
37 changed files with 13302 additions and 26 deletions
292
src/database/migrations/006_combat_system_enhancement.js
Normal file
292
src/database/migrations/006_combat_system_enhancement.js
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
/**
|
||||
* Combat System Enhancement Migration
|
||||
* Adds comprehensive combat tables and enhancements for production-ready combat system
|
||||
*/
|
||||
|
||||
exports.up = function(knex) {
|
||||
return knex.schema
|
||||
// Combat types table - defines different combat resolution types
|
||||
.createTable('combat_types', (table) => {
|
||||
table.increments('id').primary();
|
||||
table.string('name', 100).unique().notNullable();
|
||||
table.text('description');
|
||||
table.string('plugin_name', 100); // References plugins table
|
||||
table.jsonb('config');
|
||||
table.boolean('is_active').defaultTo(true);
|
||||
|
||||
table.index(['is_active']);
|
||||
table.index(['plugin_name']);
|
||||
})
|
||||
|
||||
// Main battles table - tracks all combat encounters
|
||||
.createTable('battles', (table) => {
|
||||
table.bigIncrements('id').primary();
|
||||
table.string('battle_type', 50).notNullable(); // 'fleet_vs_fleet', 'fleet_vs_colony', 'siege'
|
||||
table.string('location', 20).notNullable();
|
||||
table.integer('combat_type_id').references('combat_types.id');
|
||||
table.jsonb('participants').notNullable(); // Array of fleet/player IDs
|
||||
table.string('status', 20).notNullable().defaultTo('pending'); // 'pending', 'active', 'completed', 'cancelled'
|
||||
table.jsonb('battle_data'); // Additional battle configuration
|
||||
table.jsonb('result'); // Final battle results
|
||||
table.timestamp('started_at').defaultTo(knex.fn.now());
|
||||
table.timestamp('completed_at').nullable();
|
||||
table.timestamp('created_at').defaultTo(knex.fn.now());
|
||||
|
||||
table.index(['location']);
|
||||
table.index(['status']);
|
||||
table.index(['completed_at']);
|
||||
table.index(['started_at']);
|
||||
})
|
||||
|
||||
// Combat encounters table for detailed battle tracking
|
||||
.createTable('combat_encounters', (table) => {
|
||||
table.bigIncrements('id').primary();
|
||||
table.integer('battle_id').references('battles.id').onDelete('CASCADE');
|
||||
table.integer('attacker_fleet_id').references('fleets.id').onDelete('CASCADE').notNullable();
|
||||
table.integer('defender_fleet_id').references('fleets.id').onDelete('CASCADE');
|
||||
table.integer('defender_colony_id').references('colonies.id').onDelete('CASCADE');
|
||||
table.string('encounter_type', 50).notNullable(); // 'fleet_vs_fleet', 'fleet_vs_colony', 'siege'
|
||||
table.string('location', 20).notNullable();
|
||||
table.jsonb('initial_forces').notNullable(); // Starting forces for both sides
|
||||
table.jsonb('final_forces').notNullable(); // Remaining forces after combat
|
||||
table.jsonb('casualties').notNullable(); // Detailed casualty breakdown
|
||||
table.jsonb('combat_log').notNullable(); // Round-by-round combat log
|
||||
table.decimal('experience_gained', 10, 2).defaultTo(0);
|
||||
table.jsonb('loot_awarded'); // Resources/items awarded to winner
|
||||
table.string('outcome', 20).notNullable(); // 'attacker_victory', 'defender_victory', 'draw'
|
||||
table.integer('duration_seconds').notNullable(); // Combat duration
|
||||
table.timestamp('started_at').notNullable();
|
||||
table.timestamp('completed_at').notNullable();
|
||||
table.timestamp('created_at').defaultTo(knex.fn.now());
|
||||
|
||||
table.index(['battle_id']);
|
||||
table.index(['attacker_fleet_id']);
|
||||
table.index(['defender_fleet_id']);
|
||||
table.index(['defender_colony_id']);
|
||||
table.index(['location']);
|
||||
table.index(['outcome']);
|
||||
table.index(['started_at']);
|
||||
})
|
||||
|
||||
// Combat logs for detailed event tracking
|
||||
.createTable('combat_logs', (table) => {
|
||||
table.bigIncrements('id').primary();
|
||||
table.bigInteger('encounter_id').references('combat_encounters.id').onDelete('CASCADE').notNullable();
|
||||
table.integer('round_number').notNullable();
|
||||
table.string('event_type', 50).notNullable(); // 'damage', 'destruction', 'ability_use', 'experience_gain'
|
||||
table.jsonb('event_data').notNullable(); // Detailed event information
|
||||
table.timestamp('timestamp').defaultTo(knex.fn.now());
|
||||
|
||||
table.index(['encounter_id', 'round_number']);
|
||||
table.index(['event_type']);
|
||||
table.index(['timestamp']);
|
||||
})
|
||||
|
||||
// Combat statistics for analysis and balancing
|
||||
.createTable('combat_statistics', (table) => {
|
||||
table.bigIncrements('id').primary();
|
||||
table.integer('player_id').references('players.id').onDelete('CASCADE').notNullable();
|
||||
table.integer('battles_initiated').defaultTo(0);
|
||||
table.integer('battles_won').defaultTo(0);
|
||||
table.integer('battles_lost').defaultTo(0);
|
||||
table.integer('ships_lost').defaultTo(0);
|
||||
table.integer('ships_destroyed').defaultTo(0);
|
||||
table.bigInteger('total_damage_dealt').defaultTo(0);
|
||||
table.bigInteger('total_damage_received').defaultTo(0);
|
||||
table.decimal('total_experience_gained', 15, 2).defaultTo(0);
|
||||
table.jsonb('resources_looted').defaultTo('{}');
|
||||
table.timestamp('last_battle').nullable();
|
||||
table.timestamp('created_at').defaultTo(knex.fn.now());
|
||||
table.timestamp('updated_at').defaultTo(knex.fn.now());
|
||||
|
||||
table.index(['player_id']);
|
||||
table.index(['battles_won']);
|
||||
table.index(['last_battle']);
|
||||
})
|
||||
|
||||
// Ship combat experience and veterancy
|
||||
.createTable('ship_combat_experience', (table) => {
|
||||
table.bigIncrements('id').primary();
|
||||
table.integer('fleet_id').references('fleets.id').onDelete('CASCADE').notNullable();
|
||||
table.integer('ship_design_id').references('ship_designs.id').onDelete('CASCADE').notNullable();
|
||||
table.integer('battles_survived').defaultTo(0);
|
||||
table.integer('enemies_destroyed').defaultTo(0);
|
||||
table.bigInteger('damage_dealt').defaultTo(0);
|
||||
table.decimal('experience_points', 15, 2).defaultTo(0);
|
||||
table.integer('veterancy_level').defaultTo(1);
|
||||
table.jsonb('combat_bonuses').defaultTo('{}'); // Experience-based bonuses
|
||||
table.timestamp('last_combat').nullable();
|
||||
table.timestamp('created_at').defaultTo(knex.fn.now());
|
||||
table.timestamp('updated_at').defaultTo(knex.fn.now());
|
||||
|
||||
table.unique(['fleet_id', 'ship_design_id']);
|
||||
table.index(['fleet_id']);
|
||||
table.index(['veterancy_level']);
|
||||
table.index(['last_combat']);
|
||||
})
|
||||
|
||||
// Combat configurations for different combat types
|
||||
.createTable('combat_configurations', (table) => {
|
||||
table.increments('id').primary();
|
||||
table.string('config_name', 100).unique().notNullable();
|
||||
table.string('combat_type', 50).notNullable(); // 'instant', 'turn_based', 'real_time'
|
||||
table.jsonb('config_data').notNullable(); // Combat-specific configuration
|
||||
table.boolean('is_active').defaultTo(true);
|
||||
table.string('description', 500);
|
||||
table.timestamp('created_at').defaultTo(knex.fn.now());
|
||||
table.timestamp('updated_at').defaultTo(knex.fn.now());
|
||||
|
||||
table.index(['combat_type']);
|
||||
table.index(['is_active']);
|
||||
})
|
||||
|
||||
// Combat modifiers for temporary effects
|
||||
.createTable('combat_modifiers', (table) => {
|
||||
table.bigIncrements('id').primary();
|
||||
table.string('entity_type', 50).notNullable(); // 'fleet', 'colony', 'player'
|
||||
table.integer('entity_id').notNullable();
|
||||
table.string('modifier_type', 50).notNullable(); // 'attack_bonus', 'defense_bonus', 'speed_bonus'
|
||||
table.decimal('modifier_value', 8, 4).notNullable();
|
||||
table.string('source', 100).notNullable(); // 'technology', 'event', 'building', 'experience'
|
||||
table.timestamp('start_time').defaultTo(knex.fn.now());
|
||||
table.timestamp('end_time').nullable();
|
||||
table.boolean('is_active').defaultTo(true);
|
||||
table.jsonb('metadata'); // Additional modifier information
|
||||
|
||||
table.index(['entity_type', 'entity_id']);
|
||||
table.index(['modifier_type']);
|
||||
table.index(['is_active']);
|
||||
table.index(['end_time']);
|
||||
})
|
||||
|
||||
// Fleet positioning for tactical combat
|
||||
.createTable('fleet_positions', (table) => {
|
||||
table.bigIncrements('id').primary();
|
||||
table.integer('fleet_id').references('fleets.id').onDelete('CASCADE').notNullable();
|
||||
table.string('location', 20).notNullable();
|
||||
table.decimal('position_x', 8, 2).defaultTo(0);
|
||||
table.decimal('position_y', 8, 2).defaultTo(0);
|
||||
table.decimal('position_z', 8, 2).defaultTo(0);
|
||||
table.string('formation', 50).defaultTo('standard'); // 'standard', 'defensive', 'aggressive', 'flanking'
|
||||
table.jsonb('tactical_settings').defaultTo('{}'); // Formation-specific settings
|
||||
table.timestamp('last_updated').defaultTo(knex.fn.now());
|
||||
|
||||
table.unique(['fleet_id']);
|
||||
table.index(['location']);
|
||||
table.index(['formation']);
|
||||
})
|
||||
|
||||
// Combat queue for processing battles
|
||||
.createTable('combat_queue', (table) => {
|
||||
table.bigIncrements('id').primary();
|
||||
table.bigInteger('battle_id').references('battles.id').onDelete('CASCADE').notNullable();
|
||||
table.string('queue_status', 20).defaultTo('pending'); // 'pending', 'processing', 'completed', 'failed'
|
||||
table.integer('priority').defaultTo(100);
|
||||
table.timestamp('scheduled_at').defaultTo(knex.fn.now());
|
||||
table.timestamp('started_processing').nullable();
|
||||
table.timestamp('completed_at').nullable();
|
||||
table.integer('retry_count').defaultTo(0);
|
||||
table.text('error_message').nullable();
|
||||
table.jsonb('processing_metadata');
|
||||
|
||||
table.index(['queue_status']);
|
||||
table.index(['priority', 'scheduled_at']);
|
||||
table.index(['battle_id']);
|
||||
})
|
||||
|
||||
// Extend battles table with additional fields
|
||||
.alterTable('battles', (table) => {
|
||||
table.integer('combat_configuration_id').references('combat_configurations.id');
|
||||
table.jsonb('tactical_settings').defaultTo('{}');
|
||||
table.integer('spectator_count').defaultTo(0);
|
||||
table.jsonb('environmental_effects'); // Weather, nebulae, asteroid fields
|
||||
table.decimal('estimated_duration', 8, 2); // Estimated battle duration in seconds
|
||||
})
|
||||
|
||||
// Extend fleets table with combat-specific fields
|
||||
.alterTable('fleets', (table) => {
|
||||
table.decimal('combat_rating', 10, 2).defaultTo(0); // Calculated combat effectiveness
|
||||
table.integer('total_ship_count').defaultTo(0);
|
||||
table.jsonb('fleet_composition').defaultTo('{}'); // Ship type breakdown
|
||||
table.timestamp('last_combat').nullable();
|
||||
table.integer('combat_victories').defaultTo(0);
|
||||
table.integer('combat_defeats').defaultTo(0);
|
||||
})
|
||||
|
||||
// Extend ship_designs table with detailed combat stats
|
||||
.alterTable('ship_designs', (table) => {
|
||||
table.integer('hull_points').defaultTo(100);
|
||||
table.integer('shield_points').defaultTo(0);
|
||||
table.integer('armor_points').defaultTo(0);
|
||||
table.decimal('attack_power', 8, 2).defaultTo(10);
|
||||
table.decimal('attack_speed', 6, 2).defaultTo(1.0); // Attacks per second
|
||||
table.decimal('movement_speed', 6, 2).defaultTo(1.0);
|
||||
table.integer('cargo_capacity').defaultTo(0);
|
||||
table.jsonb('special_abilities').defaultTo('[]');
|
||||
table.jsonb('damage_resistances').defaultTo('{}');
|
||||
})
|
||||
|
||||
// Colony defense enhancements
|
||||
.alterTable('colonies', (table) => {
|
||||
table.integer('defense_rating').defaultTo(0);
|
||||
table.integer('shield_strength').defaultTo(0);
|
||||
table.boolean('under_siege').defaultTo(false);
|
||||
table.timestamp('last_attacked').nullable();
|
||||
table.integer('successful_defenses').defaultTo(0);
|
||||
table.integer('times_captured').defaultTo(0);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
return knex.schema
|
||||
// Remove added columns first
|
||||
.alterTable('colonies', (table) => {
|
||||
table.dropColumn('defense_rating');
|
||||
table.dropColumn('shield_strength');
|
||||
table.dropColumn('under_siege');
|
||||
table.dropColumn('last_attacked');
|
||||
table.dropColumn('successful_defenses');
|
||||
table.dropColumn('times_captured');
|
||||
})
|
||||
|
||||
.alterTable('ship_designs', (table) => {
|
||||
table.dropColumn('hull_points');
|
||||
table.dropColumn('shield_points');
|
||||
table.dropColumn('armor_points');
|
||||
table.dropColumn('attack_power');
|
||||
table.dropColumn('attack_speed');
|
||||
table.dropColumn('movement_speed');
|
||||
table.dropColumn('cargo_capacity');
|
||||
table.dropColumn('special_abilities');
|
||||
table.dropColumn('damage_resistances');
|
||||
})
|
||||
|
||||
.alterTable('fleets', (table) => {
|
||||
table.dropColumn('combat_rating');
|
||||
table.dropColumn('total_ship_count');
|
||||
table.dropColumn('fleet_composition');
|
||||
table.dropColumn('last_combat');
|
||||
table.dropColumn('combat_victories');
|
||||
table.dropColumn('combat_defeats');
|
||||
})
|
||||
|
||||
.alterTable('battles', (table) => {
|
||||
table.dropColumn('combat_configuration_id');
|
||||
table.dropColumn('tactical_settings');
|
||||
table.dropColumn('spectator_count');
|
||||
table.dropColumn('environmental_effects');
|
||||
table.dropColumn('estimated_duration');
|
||||
})
|
||||
|
||||
// Drop new tables
|
||||
.dropTableIfExists('combat_queue')
|
||||
.dropTableIfExists('fleet_positions')
|
||||
.dropTableIfExists('combat_modifiers')
|
||||
.dropTableIfExists('combat_configurations')
|
||||
.dropTableIfExists('ship_combat_experience')
|
||||
.dropTableIfExists('combat_statistics')
|
||||
.dropTableIfExists('combat_logs')
|
||||
.dropTableIfExists('combat_encounters')
|
||||
.dropTableIfExists('battles')
|
||||
.dropTableIfExists('combat_types');
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue