-- ============================================================================= -- Realm Avatars and Forced Avatar Support -- ============================================================================= -- Pre-configured avatar configurations specific to a realm. -- These reference realm.props directly (not inventory items). -- -- Also adds forced avatar support to auth.active_avatars and realm.scenes. -- -- Loaded after 030_realm.sql (realm.props must exist) -- ============================================================================= -- ============================================================================= -- Realm Avatars Table -- ============================================================================= CREATE TABLE realm.avatars ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), realm_id UUID NOT NULL REFERENCES realm.realms(id) ON DELETE CASCADE, slug public.slug NOT NULL, name public.nonempty_text NOT NULL, description TEXT, is_public BOOLEAN NOT NULL DEFAULT false, is_active BOOLEAN NOT NULL DEFAULT true, thumbnail_path public.asset_path, -- Content layers: 3 layers x 9 positions = 27 slots -- All reference realm.props(id) directly (realm props only, no server props) l_skin_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_skin_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_skin_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_skin_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_skin_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_skin_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_skin_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_skin_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_skin_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_clothes_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_clothes_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_clothes_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_clothes_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_clothes_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_clothes_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_clothes_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_clothes_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_clothes_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_accessories_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_accessories_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_accessories_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_accessories_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_accessories_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_accessories_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_accessories_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_accessories_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, l_accessories_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, -- Emotion layers: 12 emotions x 9 positions = 108 slots e_neutral_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_neutral_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_neutral_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_neutral_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_neutral_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_neutral_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_neutral_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_neutral_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_neutral_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_happy_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_happy_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_happy_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_happy_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_happy_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_happy_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_happy_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_happy_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_happy_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sad_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sad_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sad_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sad_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sad_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sad_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sad_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sad_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sad_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_angry_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_angry_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_angry_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_angry_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_angry_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_angry_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_angry_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_angry_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_angry_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_surprised_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_surprised_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_surprised_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_surprised_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_surprised_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_surprised_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_surprised_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_surprised_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_surprised_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_thinking_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_thinking_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_thinking_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_thinking_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_thinking_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_thinking_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_thinking_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_thinking_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_thinking_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_laughing_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_laughing_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_laughing_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_laughing_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_laughing_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_laughing_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_laughing_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_laughing_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_laughing_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_crying_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_crying_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_crying_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_crying_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_crying_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_crying_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_crying_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_crying_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_crying_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_love_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_love_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_love_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_love_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_love_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_love_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_love_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_love_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_love_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_confused_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_confused_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_confused_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_confused_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_confused_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_confused_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_confused_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_confused_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_confused_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sleeping_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sleeping_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sleeping_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sleeping_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sleeping_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sleeping_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sleeping_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sleeping_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_sleeping_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_wink_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_wink_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_wink_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_wink_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_wink_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_wink_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_wink_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_wink_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL, e_wink_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL, created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT uq_realm_avatars_slug UNIQUE (realm_id, slug) ); COMMENT ON TABLE realm.avatars IS 'Pre-configured avatar configurations specific to a realm (references realm.props only)'; CREATE INDEX idx_realm_avatars_realm ON realm.avatars (realm_id); CREATE INDEX idx_realm_avatars_active ON realm.avatars (realm_id, is_active) WHERE is_active = true; CREATE INDEX idx_realm_avatars_public ON realm.avatars (realm_id, is_public) WHERE is_public = true; COMMENT ON COLUMN realm.avatars.is_public IS 'When true, avatar appears in the public avatar selection for this realm. Uses filtered index.'; -- ============================================================================= -- Add Default Avatar FK Constraints to realm.realms -- ============================================================================= -- Now that realm.avatars exists, add the FK constraints for default avatars. -- ============================================================================= ALTER TABLE realm.realms ADD CONSTRAINT fk_realm_default_avatar_neutral_child FOREIGN KEY (default_avatar_neutral_child) REFERENCES realm.avatars(id) ON DELETE SET NULL, ADD CONSTRAINT fk_realm_default_avatar_neutral_adult FOREIGN KEY (default_avatar_neutral_adult) REFERENCES realm.avatars(id) ON DELETE SET NULL, ADD CONSTRAINT fk_realm_default_avatar_male_child FOREIGN KEY (default_avatar_male_child) REFERENCES realm.avatars(id) ON DELETE SET NULL, ADD CONSTRAINT fk_realm_default_avatar_male_adult FOREIGN KEY (default_avatar_male_adult) REFERENCES realm.avatars(id) ON DELETE SET NULL, ADD CONSTRAINT fk_realm_default_avatar_female_child FOREIGN KEY (default_avatar_female_child) REFERENCES realm.avatars(id) ON DELETE SET NULL, ADD CONSTRAINT fk_realm_default_avatar_female_adult FOREIGN KEY (default_avatar_female_adult) REFERENCES realm.avatars(id) ON DELETE SET NULL; COMMENT ON COLUMN realm.realms.default_avatar_neutral_child IS 'Default realm avatar for gender-neutral child users (overrides server default)'; COMMENT ON COLUMN realm.realms.default_avatar_neutral_adult IS 'Default realm avatar for gender-neutral adult users (overrides server default)'; COMMENT ON COLUMN realm.realms.default_avatar_male_child IS 'Default realm avatar for male child users (overrides server default)'; COMMENT ON COLUMN realm.realms.default_avatar_male_adult IS 'Default realm avatar for male adult users (overrides server default)'; COMMENT ON COLUMN realm.realms.default_avatar_female_child IS 'Default realm avatar for female child users (overrides server default)'; COMMENT ON COLUMN realm.realms.default_avatar_female_adult IS 'Default realm avatar for female adult users (overrides server default)'; -- ============================================================================= -- Add FK for auth.active_avatars.selected_realm_avatar_id -- ============================================================================= ALTER TABLE auth.active_avatars ADD CONSTRAINT fk_auth_active_avatars_selected_realm_avatar FOREIGN KEY (selected_realm_avatar_id) REFERENCES realm.avatars(id) ON DELETE SET NULL; -- ============================================================================= -- Add Forced Avatar Columns to auth.active_avatars -- ============================================================================= -- Tracks when a user has a forced avatar (from mod command or scene entry) ALTER TABLE auth.active_avatars ADD COLUMN forced_avatar_id UUID, ADD COLUMN forced_avatar_source TEXT CHECK (forced_avatar_source IS NULL OR forced_avatar_source IN ('server', 'realm', 'scene')), ADD COLUMN forced_by UUID REFERENCES auth.users(id) ON DELETE SET NULL, ADD COLUMN forced_until TIMESTAMPTZ; COMMENT ON COLUMN auth.active_avatars.forced_avatar_id IS 'UUID of the forced avatar (from server.avatars or realm.avatars depending on source)'; COMMENT ON COLUMN auth.active_avatars.forced_avatar_source IS 'Source of forced avatar: server (server.avatars), realm (realm.avatars), or scene (from scene setting)'; COMMENT ON COLUMN auth.active_avatars.forced_by IS 'User who forced this avatar (moderator), NULL for scene-forced'; COMMENT ON COLUMN auth.active_avatars.forced_until IS 'When the forced avatar expires, NULL for permanent/until manually cleared'; -- Index for finding expired forced avatars CREATE INDEX idx_auth_active_avatars_forced_expires ON auth.active_avatars (forced_until) WHERE forced_until IS NOT NULL; -- ============================================================================= -- Add Forced Avatar Columns to realm.scenes -- ============================================================================= -- Allows scenes to force all users to wear a specific avatar ALTER TABLE realm.scenes ADD COLUMN forced_avatar_id UUID, ADD COLUMN forced_avatar_source TEXT CHECK (forced_avatar_source IS NULL OR forced_avatar_source IN ('server', 'realm')); COMMENT ON COLUMN realm.scenes.forced_avatar_id IS 'UUID of avatar all users must wear in this scene (from server.avatars or realm.avatars)'; COMMENT ON COLUMN realm.scenes.forced_avatar_source IS 'Source of forced avatar: server (server.avatars) or realm (realm.avatars)'; -- ============================================================================= -- Add New Moderation Action Types -- ============================================================================= ALTER TYPE server.action_type ADD VALUE 'dress_user'; ALTER TYPE server.action_type ADD VALUE 'undress_user'; ALTER TYPE server.action_type ADD VALUE 'teleport'; COMMENT ON TYPE server.action_type IS 'Type of moderation action taken (includes dress_user, undress_user, teleport)';