feat: model specialization combat effects
This commit is contained in:
+140
-1
@@ -242,6 +242,7 @@ INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-metric
|
||||
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-system-notifications-manage','system.notifications.manage','View and edit automated ISAC system notification messages');
|
||||
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-images-manage','images.manage','View, upload, rename, tag, and categorize managed image assets');
|
||||
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-avatars-review','avatars.review','Review and remove custom user avatars');
|
||||
INSERT OR IGNORE INTO permissions(id,name,description) VALUES('permission-calculation-publish','calculation.publish','Activate combat calculation revisions used by every loadout');
|
||||
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-editor','permission-isac-access');
|
||||
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-editor','permission-data-manage');
|
||||
INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-data-editor','permission-data-view');
|
||||
@@ -277,7 +278,7 @@ INSERT OR IGNORE INTO role_permissions(role_id,permission_id) VALUES('role-moder
|
||||
CREATE TABLE IF NOT EXISTS oauth_states (state_hash TEXT PRIMARY KEY, provider TEXT NOT NULL, mode TEXT NOT NULL, user_id TEXT REFERENCES users(id) ON DELETE CASCADE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS data_change_proposals (
|
||||
id TEXT PRIMARY KEY,
|
||||
module TEXT NOT NULL CHECK(module IN ('configuration','attributes','talents','brands','gear','weapons','weaponmods','gearmods','protocols','skillchips','specializations')),
|
||||
module TEXT NOT NULL CHECK(module IN ('configuration','calculations','attributes','talents','brands','gear','weapons','weaponmods','gearmods','protocols','skillchips','specializations')),
|
||||
action TEXT NOT NULL,
|
||||
entity_id TEXT,
|
||||
draft_entity_id TEXT,
|
||||
@@ -922,6 +923,144 @@ CREATE TABLE IF NOT EXISTS specialization_tree_rank_costs (
|
||||
PRIMARY KEY(node_id,rank,resource_id),
|
||||
FOREIGN KEY(node_id,rank) REFERENCES specialization_tree_node_ranks(node_id,rank) ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS specialization_combat_effects (
|
||||
id TEXT PRIMARY KEY,
|
||||
specialization_id TEXT NOT NULL REFERENCES specializations(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description_template TEXT NOT NULL,
|
||||
active INTEGER NOT NULL DEFAULT 1 CHECK(active IN (0,1)),
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
UNIQUE(specialization_id,name)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS specialization_combat_effects_specialization_idx
|
||||
ON specialization_combat_effects(specialization_id,active,name);
|
||||
CREATE TABLE IF NOT EXISTS specialization_tree_rank_combat_effects (
|
||||
id TEXT PRIMARY KEY,
|
||||
node_id TEXT NOT NULL,
|
||||
rank INTEGER NOT NULL,
|
||||
combat_effect_id TEXT NOT NULL REFERENCES specialization_combat_effects(id),
|
||||
effect_order INTEGER NOT NULL CHECK(effect_order>=1),
|
||||
trigger_type TEXT NOT NULL CHECK(trigger_type IN ('enemy_hits_within_window')),
|
||||
trigger_count INTEGER NOT NULL CHECK(trigger_count>0),
|
||||
trigger_window_seconds REAL NOT NULL CHECK(trigger_window_seconds>0),
|
||||
duration_seconds REAL NOT NULL CHECK(duration_seconds>0),
|
||||
max_stacks INTEGER NOT NULL CHECK(max_stacks>0),
|
||||
stacks_per_trigger INTEGER NOT NULL DEFAULT 1 CHECK(stacks_per_trigger>0),
|
||||
counter_policy TEXT NOT NULL DEFAULT 'reset_on_proc' CHECK(counter_policy IN ('reset_on_proc')),
|
||||
refresh_policy TEXT NOT NULL DEFAULT 'refresh_on_stack_gain' CHECK(refresh_policy IN ('refresh_on_stack_gain')),
|
||||
expiry_policy TEXT NOT NULL DEFAULT 'clear_all' CHECK(expiry_policy IN ('clear_all')),
|
||||
FOREIGN KEY(node_id,rank) REFERENCES specialization_tree_node_ranks(node_id,rank) ON DELETE CASCADE,
|
||||
UNIQUE(node_id,rank,effect_order)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS specialization_tree_rank_combat_effects_effect_idx
|
||||
ON specialization_tree_rank_combat_effects(combat_effect_id);
|
||||
CREATE TABLE IF NOT EXISTS specialization_tree_rank_combat_modifiers (
|
||||
id TEXT PRIMARY KEY,
|
||||
rank_combat_effect_id TEXT NOT NULL REFERENCES specialization_tree_rank_combat_effects(id) ON DELETE CASCADE,
|
||||
modifier_order INTEGER NOT NULL CHECK(modifier_order>=1),
|
||||
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
||||
value_per_stack REAL NOT NULL,
|
||||
unit TEXT,
|
||||
UNIQUE(rank_combat_effect_id,modifier_order)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS calculation_revisions (
|
||||
id TEXT PRIMARY KEY,
|
||||
version INTEGER NOT NULL UNIQUE CHECK(version>0),
|
||||
name TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'draft' CHECK(status IN ('draft','active','retired')),
|
||||
notes TEXT,
|
||||
based_on_revision_id TEXT REFERENCES calculation_revisions(id),
|
||||
created_by TEXT REFERENCES users(id),
|
||||
created_at TEXT NOT NULL,
|
||||
published_by TEXT REFERENCES users(id),
|
||||
published_at TEXT
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS calculation_revisions_active_idx
|
||||
ON calculation_revisions(status) WHERE status='active';
|
||||
CREATE TABLE IF NOT EXISTS calculation_stages (
|
||||
id TEXT PRIMARY KEY,
|
||||
revision_id TEXT NOT NULL REFERENCES calculation_revisions(id) ON DELETE CASCADE,
|
||||
stage_key TEXT NOT NULL,
|
||||
label TEXT NOT NULL,
|
||||
operator TEXT NOT NULL CHECK(operator IN (
|
||||
'base_damage','flat_add','additive_percent','multiplicative_percent',
|
||||
'expected_critical','expected_headshot','rate_of_fire','magazine_size',
|
||||
'reload_speed','firing_cycle'
|
||||
)),
|
||||
display_order INTEGER NOT NULL CHECK(display_order>=1),
|
||||
locked_position TEXT CHECK(locked_position IS NULL OR locked_position IN ('first','last')),
|
||||
UNIQUE(revision_id,stage_key),
|
||||
UNIQUE(revision_id,display_order)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS attribute_calculation_profiles (
|
||||
id TEXT PRIMARY KEY,
|
||||
revision_id TEXT NOT NULL REFERENCES calculation_revisions(id) ON DELETE CASCADE,
|
||||
stat_attribute_id TEXT NOT NULL REFERENCES stat_attributes(id),
|
||||
stage_key TEXT NOT NULL,
|
||||
weight REAL NOT NULL DEFAULT 1,
|
||||
soft_cap REAL,
|
||||
hard_cap REAL,
|
||||
curve_type TEXT NOT NULL DEFAULT 'none' CHECK(curve_type IN ('none','power','smoothstep')),
|
||||
curve_parameter REAL,
|
||||
weapon_category TEXT NOT NULL DEFAULT '*',
|
||||
UNIQUE(revision_id,stat_attribute_id,stage_key,weapon_category),
|
||||
FOREIGN KEY(revision_id,stage_key) REFERENCES calculation_stages(revision_id,stage_key) ON DELETE CASCADE,
|
||||
CHECK(soft_cap IS NULL OR soft_cap>=0),
|
||||
CHECK(hard_cap IS NULL OR hard_cap>=0),
|
||||
CHECK(soft_cap IS NULL OR hard_cap IS NULL OR hard_cap>=soft_cap)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS calculation_calibration_cases (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'draft' CHECK(status IN ('draft','ready')),
|
||||
loadout_snapshot_json TEXT NOT NULL,
|
||||
selected_weapon INTEGER NOT NULL DEFAULT 0 CHECK(selected_weapon IN (0,1)),
|
||||
game_version TEXT,
|
||||
observed_at TEXT,
|
||||
notes TEXT,
|
||||
body_hit REAL,
|
||||
critical_body_hit REAL,
|
||||
headshot REAL,
|
||||
critical_headshot REAL,
|
||||
magazine_seconds REAL,
|
||||
reload_seconds REAL,
|
||||
encounter_dps REAL,
|
||||
predicted_json TEXT,
|
||||
calculation_revision_id TEXT REFERENCES calculation_revisions(id),
|
||||
created_by TEXT NOT NULL REFERENCES users(id),
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
CHECK(body_hit IS NULL OR body_hit>=0),
|
||||
CHECK(critical_body_hit IS NULL OR critical_body_hit>=0),
|
||||
CHECK(headshot IS NULL OR headshot>=0),
|
||||
CHECK(critical_headshot IS NULL OR critical_headshot>=0),
|
||||
CHECK(magazine_seconds IS NULL OR magazine_seconds>0),
|
||||
CHECK(reload_seconds IS NULL OR reload_seconds>0),
|
||||
CHECK(encounter_dps IS NULL OR encounter_dps>=0)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS calculation_calibration_status_idx
|
||||
ON calculation_calibration_cases(status,updated_at DESC);
|
||||
INSERT OR IGNORE INTO calculation_revisions(id,version,name,status,notes,created_at,published_at)
|
||||
VALUES('calculation-revision-1',1,'Initial combat model','active','Typed baseline combat stages','2026-07-26T00:00:00.000Z','2026-07-26T00:00:00.000Z');
|
||||
INSERT OR IGNORE INTO calculation_stages(id,revision_id,stage_key,label,operator,display_order,locked_position)
|
||||
VALUES('calculation-stage-base-1','calculation-revision-1','base_damage','Base damage','base_damage',1,'first');
|
||||
INSERT OR IGNORE INTO calculation_stages(id,revision_id,stage_key,label,operator,display_order)
|
||||
VALUES('calculation-stage-flat-1','calculation-revision-1','flat_damage','Flat damage','flat_add',2);
|
||||
INSERT OR IGNORE INTO calculation_stages(id,revision_id,stage_key,label,operator,display_order)
|
||||
VALUES('calculation-stage-additive-1','calculation-revision-1','additive_damage','Additive damage','additive_percent',3);
|
||||
INSERT OR IGNORE INTO calculation_stages(id,revision_id,stage_key,label,operator,display_order)
|
||||
VALUES('calculation-stage-multiplicative-1','calculation-revision-1','multiplicative_damage','Multiplicative damage','multiplicative_percent',4);
|
||||
INSERT OR IGNORE INTO calculation_stages(id,revision_id,stage_key,label,operator,display_order)
|
||||
VALUES('calculation-stage-critical-chance-1','calculation-revision-1','critical_chance','Critical chance','expected_critical',5);
|
||||
INSERT OR IGNORE INTO calculation_stages(id,revision_id,stage_key,label,operator,display_order)
|
||||
VALUES('calculation-stage-critical-1','calculation-revision-1','critical_damage','Critical damage','expected_critical',6);
|
||||
INSERT OR IGNORE INTO calculation_stages(id,revision_id,stage_key,label,operator,display_order)
|
||||
VALUES('calculation-stage-headshot-chance-1','calculation-revision-1','headshot_chance','Headshot chance','expected_headshot',7);
|
||||
INSERT OR IGNORE INTO calculation_stages(id,revision_id,stage_key,label,operator,display_order)
|
||||
VALUES('calculation-stage-headshot-1','calculation-revision-1','headshot_damage','Headshot damage','expected_headshot',8);
|
||||
INSERT OR IGNORE INTO calculation_stages(id,revision_id,stage_key,label,operator,display_order,locked_position)
|
||||
VALUES('calculation-stage-cycle-1','calculation-revision-1','firing_cycle','Firing cycle','firing_cycle',9,'last');
|
||||
CREATE TABLE IF NOT EXISTS game_data_icons (
|
||||
entity_type TEXT NOT NULL CHECK(entity_type IN ('talent', 'brand_set', 'os_protocol', 'skill_chip', 'specialization', 'specialization_ability', 'specialization_node')),
|
||||
entity_id TEXT NOT NULL,
|
||||
|
||||
Reference in New Issue
Block a user