@@@RTN@@@58085
f
_add_item_delete_log
in_item_id bigint, in_inventory_id bigint, in_template_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._add_item_delete_log(in_item_id bigint, in_inventory_id bigint, in_template_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM _add_item_trace_log('delete_item', in_item_id, in_inventory_id, in_template_id);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58086
f
_add_item_trace_log
in_function_name dune.itemtrackingfunctiontype, in_item_locations dune.inventoryitemlocation[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._add_item_trace_log(in_function_name dune.itemtrackingfunctiontype, in_item_locations dune.inventoryitemlocation[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    IF coalesce(current_setting('dune.item_tracking_enabled', true)::BOOLEAN, false) IS FALSE THEN
        return;
    END IF;
    
    INSERT INTO item_operations_staging_table (
        function_name,
        item_id,
        account_id,
        inventory_id,
        template_id,
        event_time,
        position_index
    )
    SELECT
        in_function_name,
        (loc).item_id,
        act.owner_account_id,
        (loc).inventory_id,
        NULL, -- template_id
        now(),
        (loc).position_index
    FROM UNNEST(in_item_locations) AS loc
    JOIN inventories inv ON inv.id = (loc).inventory_id
    JOIN actors act ON act.id = inv.actor_id;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58087
f
_add_item_trace_log
in_function_name dune.itemtrackingfunctiontype, in_item_id bigint, in_inventory_id bigint, in_template_id text, in_position_index bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._add_item_trace_log(in_function_name dune.itemtrackingfunctiontype, in_item_id bigint, in_inventory_id bigint, in_template_id text, in_position_index bigint DEFAULT NULL::bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    owner_account_id BIGINT;
BEGIN
    IF coalesce(current_setting('dune.item_tracking_enabled', true)::BOOLEAN, false) IS FALSE THEN
        return;
    END IF;
    
    -- get item owner's account id
    SELECT act.owner_account_id
    INTO owner_account_id
    FROM inventories inv
    JOIN actors act ON act.id = inv.actor_id
    WHERE inv.id = in_inventory_id;

    INSERT INTO item_operations_staging_table (
        function_name,
        item_id,
        account_id,
        inventory_id,
        template_id,
        event_time,
        position_index
    ) VALUES (
        in_function_name,
        in_item_id,
        owner_account_id,
        in_inventory_id,
        in_template_id,
        now(),
        in_position_index
    );
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58094
f
_building_validate_totem_owner_id
in_totem_owner_id bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._building_validate_totem_owner_id(in_totem_owner_id bigint)
 RETURNS bigint
 LANGUAGE sql
BEGIN ATOMIC
 SELECT
         CASE
             WHEN (in_totem_owner_id = 0) THEN NULL::bigint
             WHEN (EXISTS ( SELECT 1
                FROM dune.fgl_entities
               WHERE (fgl_entities.entity_id = _building_validate_totem_owner_id.in_totem_owner_id))) THEN in_totem_owner_id
             ELSE NULL::bigint
         END AS "case";
END

@@@ENDRTN@@@
@@@RTN@@@58095
f
_character_transfer_allocate_id
kind dune._charactertransferentrykind, data jsonb
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_allocate_id(kind dune._charactertransferentrykind, data jsonb)
 RETURNS bigint
 LANGUAGE sql
 IMMUTABLE
AS $function$
	select case
		when kind = 'acc' then nextval('encrypted_accounts_id_seq')
		when kind = 'act' then nextval('actors_id_seq')
		when kind = 'inv' then nextval('inventories_id_seq')
		when kind = 'itm' then nextval('items_id_seq')
		when kind = 'fgl' then nextval('character_transfer_fgl_entities_entity_id_seq')
		when kind = 'bbp' then nextval('building_blueprints_id_seq')

		when kind = 'VehicleModule' then nextval('vehicle_modules_id_seq')

		when kind = 'Faction' then (select id from factions where "name" = data->>'name')
		when kind = 'Tutorial' then (select id from tutorials where "name" = data->>'name')
		when kind = 'Keystone' then (select id from specialization_keystones_map where "name" = data->>'name')

		when kind = 'BaseBackup' then nextval('base_backups_id_seq')
		when kind = 'TaxInvoice' then nextval('tax_invoice_id_seq')

		when kind = 'DungeonCompletion' then nextval('dungeon_completion_completion_id_seq')
		else null
	end;
$function$

@@@ENDRTN@@@
@@@RTN@@@58096
f
_character_transfer_create_data_table

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_create_data_table()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	if not exists (
		select 1 from pg_catalog.pg_tables
		where schemaname=(select nspname from pg_catalog.pg_namespace where oid=pg_my_temp_schema())
		and tablename = 'export_data'
	) then
	    create temporary table if not exists pg_temp.export_data (
	        "id" BigInt DEFAULT NULL,
	        "transfer_id" BigSerial PRIMARY KEY NOT NULL,
	        "kind" _CharacterTransferEntryKind NOT NULL,
	        "data" JsonB NOT NULL
	    ) on commit drop;
		create index on pg_temp.export_data("id");
		create index on pg_temp.export_data("kind");
	end if;
	alter sequence pg_temp.export_data_transfer_id_seq restart with 1;
	truncate pg_temp.export_data;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58097
f
_character_transfer_data_filter
id text, removed text[], VARIADIC refs dune._charactertransferdatafilterref[]
dune._charactertransferdatafilter

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_data_filter(id text, removed text[], VARIADIC refs dune._charactertransferdatafilterref[] DEFAULT '{}'::dune._charactertransferdatafilterref[])
 RETURNS dune._charactertransferdatafilter
 LANGUAGE sql
 IMMUTABLE
AS $function$
	select (id, removed, refs)::_CharacterTransferDataFilter;
$function$

@@@ENDRTN@@@
@@@RTN@@@58098
f
_character_transfer_data_table_load
entries jsonb
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_data_table_load(entries jsonb)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	with parsed as (
		select
			(entry->>'id')::BigInt as transfer_id,
			(entry->>'kind')::_CharacterTransferEntryKind as kind,
			(entry->'data') as data
		from jsonb_array_elements(entries) as entry
	)
	insert into pg_temp.export_data("id", "transfer_id", "kind", "data")
		select _character_transfer_allocate_id(kind, data) as id, transfer_id, kind, data
		from parsed;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58099
f
_character_transfer_data_table_save

jsonb

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_data_table_save()
 RETURNS jsonb
 LANGUAGE plpgsql
AS $function$
begin
	return (select jsonb_agg(jsonb_build_object('id', transfer_id, 'kind', kind, 'data', data)) from pg_temp.export_data);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58100
f
_character_transfer_ensure_player_is_owner_of_vbt_vehicle
in_vehicle_id bigint[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_ensure_player_is_owner_of_vbt_vehicle(in_vehicle_id bigint[])
 RETURNS void
 LANGUAGE sql
AS $function$
	update permission_actor_rank set "rank" = 1 where permission_actor_id = any(in_vehicle_id);
$function$

@@@ENDRTN@@@
@@@RTN@@@58101
f
_character_transfer_get_filter
kind dune._charactertransferentrykind
dune._charactertransferdatafilter

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_get_filter(kind dune._charactertransferentrykind)
 RETURNS dune._charactertransferdatafilter
 LANGUAGE sql
 IMMUTABLE
AS $function$
	select case
		when kind = 'acc' then _character_transfer_data_filter('id', '{user,funcom_id}')
		when kind = 'act' then _character_transfer_data_filter('id', '{}', ('owner_account_id', 'acc', false))
		when kind = 'inv' then _character_transfer_data_filter(
			'id',
			'{exchange_id,item_id}',
			('actor_id', 'act', false),
			('vehicle_module_id', 'VehicleModule', false)
		)
		when kind = 'itm' then _character_transfer_data_filter(
			'id', '{}',
			('inventory_id', 'inv', true)
		)
		when kind = 'fgl' then _character_transfer_data_filter(
			'entity_id', '{}',
			('actor_id', 'act', true)
		)
		when kind = 'bbp' then _character_transfer_data_filter(
			'id', '{}',
			('item_id', 'itm', true),
			('player_id', 'act', false)
		)

		when kind = 'Faction' then _character_transfer_data_filter('id', '{}')
		when kind = 'Tutorial' then _character_transfer_data_filter('id', '{}')
		when kind = 'Keystone' then _character_transfer_data_filter('id', '{}')

		when kind = 'Character' then _character_transfer_data_filter(
			null, '{character_name}',
			('account_id', 'acc', true),
			('player_pawn_id', 'act', true),
			('player_controller_id', 'act', true),
			('player_state_id', 'act', true)
		)
		when kind = 'RespawnLocation' then _character_transfer_data_filter(
			null, '{}',
			('account_id', 'acc', true)
		)
		when kind = 'PlayerMarker' then _character_transfer_data_filter(null, '{}', ('player_id', 'act', true))
		when kind = 'Marker' then _character_transfer_data_filter(null, '{}')
		when kind = 'DialogueMetNpc' then _character_transfer_data_filter(null, '{}', ('player_id', 'act', true))
		when kind = 'DialogueTakenNode' then _character_transfer_data_filter(null, '{}', ('player_id', 'act', true))

		when kind = 'PlayerFaction' then _character_transfer_data_filter(
			null, '{}',
			('actor_id', 'act', true), ('faction_id', 'Faction', true)
		)
		when kind = 'PlayerFactionReputation' then _character_transfer_data_filter(
			null, '{}',
			('actor_id', 'act', true), ('faction_id', 'Faction', true)
		)
		when kind = 'ConsumedLore' then _character_transfer_data_filter(null, '{}', ('actor_id', 'act', true))
		when kind = 'PlayerTutorial' then _character_transfer_data_filter(null, '{}', ('player_id', 'act', true), ('tutorial_id', 'Tutorial', true))
        when kind = 'PurchasedKeystone' then _character_transfer_data_filter(null, '{}', ('player_id', 'act', true), ('keystone_id', 'Keystone', true))
		when kind = 'SpecializationTracks' then _character_transfer_data_filter(null, '{}', ('player_id', 'act', true))
		when kind = 'SpecializationRefund' then _character_transfer_data_filter(null, '{}', ('player_id', 'act', true))

		when kind = 'BuildingFavorite' then _character_transfer_data_filter(null, '{}', ('account_id', 'acc', true))
		when kind = 'BuildingProgression' then _character_transfer_data_filter(null, '{}', ('account_id', 'acc', true))
		when kind = 'CommuninetPlayer' then _character_transfer_data_filter(null, '{}', ('account_id', 'acc', true))
		when kind = 'CommuninetPlayerChannel' then _character_transfer_data_filter(null, '{}', ('account_id', 'acc', true))
		when kind = 'JourneyStoryNode' then _character_transfer_data_filter(null, '{}', ('account_id', 'acc', true))
		when kind = 'MapArea' then _character_transfer_data_filter(null, '{}', ('account_id', 'acc', true))
		when kind = 'PlayerAccessCode' then _character_transfer_data_filter(null, '{}', ('account_id', 'acc', true))
		when kind = 'PlayerTag' then _character_transfer_data_filter(null, '{}', ('account_id', 'acc', true))

		when kind = 'ActorInventory' then _character_transfer_data_filter(null, '{}', ('inventory_id', 'inv', true))
		when kind = 'Sinkchart' then _character_transfer_data_filter(null, '{}', ('item_id', 'itm', true))

		when kind = 'BackupVehicle' then _character_transfer_data_filter(null, '{}', ('account_id', 'acc', true), ('vehicle_id', 'act', true))
		when kind = 'RecoveredVehicle' then _character_transfer_data_filter(null, '{}', ('account_id', 'acc', true), ('vehicle_id', 'act', true))
		when kind = 'Vehicle' then _character_transfer_data_filter(null, '{}', ('id', 'act', true))
		when kind = 'VehicleModule' then _character_transfer_data_filter('id', '{}', ('vehicle_id', 'act', true))
		when kind = 'VehicleModuleInventory' then _character_transfer_data_filter(null, '{}', ('inventory_id', 'inv', true))

		when kind = 'ActorState' then _character_transfer_data_filter(null, '{}', ('actor_id', 'act', true))

		when kind = 'PermissionActor' then _character_transfer_data_filter(null, '{}', ('actor_id', 'act', true))
		when kind = 'PermissionActorRank' then _character_transfer_data_filter(null, '{}', ('permission_actor_id', 'act', true), ('player_id', 'act', true))

		when kind = 'BuildingBlueprintInstance' then _character_transfer_data_filter(null, '{}', ('building_blueprint_id', 'bbp', true))
		when kind = 'BuildingBlueprintPlaceable' then _character_transfer_data_filter(null, '{}', ('building_blueprint_id', 'bbp', true))
		when kind = 'BuildingBlueprintPentashield' then _character_transfer_data_filter(null, '{}', ('building_blueprint_id', 'bbp', true))

		when kind = 'Building' then _character_transfer_data_filter(null, '{}', ('id', 'act', true))
		when kind = 'BuildingInstance' then _character_transfer_data_filter(null, '{}', ('building_id', 'act', true), ('owner_entity_id', 'fgl', false))
		when kind = 'Placeable' then _character_transfer_data_filter(null, '{}', ('id', 'act', true), ('owner_entity_id', 'fgl', false))
		when kind = 'BaseBackup' then _character_transfer_data_filter('id', '{}', ('player_id', 'act', true))
		when kind = 'BaseBackupLinkedActor' then _character_transfer_data_filter(null, '{}', ('id', 'BaseBackup', true), ('actor_id', 'act', true))
		when kind = 'LandclaimSegment' then _character_transfer_data_filter(null, '{}', ('totem_id', 'act', true))
		when kind = 'TaxInvoice' then _character_transfer_data_filter('id', '{}', ('totem_id', 'act', true))

		when kind = 'PlayerVirtualCurrencyBalance' then _character_transfer_data_filter(null, '{}', ('player_controller_id', 'act', true))

		when kind = 'DungeonCompletion' then _character_transfer_data_filter('completion_id', '{}')
		when kind = 'DungeonCompletionPlayer' then _character_transfer_data_filter(null, '{}', ('player_id', 'act', true), ('completion_id', 'DungeonCompletion', true))
		when kind = 'Totem' then _character_transfer_data_filter(null, '{}', ('id', 'act', true))

        when kind = 'LandsraadHouseRewards' then _character_transfer_data_filter(null, '{}', ('player_id', 'act', true))
	end;
$function$

@@@ENDRTN@@@
@@@RTN@@@58102
f
_character_transfer_get_patches_checksum

text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_get_patches_checksum()
 RETURNS text
 LANGUAGE plpgsql
AS $function$
begin
	return (SELECT md5(coalesce(string_agg("name", ',' ORDER BY "name"), '')) FROM applied_patches);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58103
f
_character_transfer_pre_export_validation
in_fls_id text
TABLE(out_acc_id bigint, out_funcom_id text, out_player_controller_id bigint, out_player_pawn_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_pre_export_validation(in_fls_id text)
 RETURNS TABLE(out_acc_id bigint, out_funcom_id text, out_player_controller_id bigint, out_player_pawn_id bigint)
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_acc_id BigInt;
    v_funcom_id Text;
	v_player_controller_id BigInt;
	v_player_pawn_id BigInt;
BEGIN
	if not (select is_player_offline(in_fls_id)) then
        raise exception 'sbRF3$ - player_online: Player % must be Offline', in_fls_id;
    end if;

	v_acc_id := (select id from accounts where "user" = in_fls_id);
    IF v_acc_id is null THEN
        RAISE EXCEPTION 'sbFV2$ - unknown_fls_id: FLS ID % not found', in_fls_id;
    END IF;

    v_funcom_id := (select funcom_id from accounts where "user" = in_fls_id);
    IF v_funcom_id is null THEN
        RAISE EXCEPTION 'sb9X3$ - missing_funcom_id: Player % does not have a funcomId', in_fls_id;
    END IF;

	select into v_player_controller_id, v_player_pawn_id
		player_controller_id, player_pawn_id from player_state where account_id=v_acc_id;
	if v_player_controller_id is null then
		raise exception 'sbH84$ - missing_controller: Player % does not have a controller', in_fls_id;
	end if;
	if v_player_pawn_id is null then
		raise exception 'sbHF3$ - missing_character: Player % does not have a character', in_fls_id;
	end if;

	return query select v_acc_id, v_funcom_id, v_player_controller_id, v_player_pawn_id;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58104
f
_character_transfer_property_not_exported_is_expected
path text
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_property_not_exported_is_expected(path text)
 RETURNS boolean
 LANGUAGE sql
 IMMUTABLE
AS $function$
	select (path = ANY(ARRAY[
		'.properties.BP_DunePlayerCharacter_C.m_CurrentVehicleId',
		'.properties.WeaponActorComponent.m_FavoriteWeaponItemDatabaseId',
		'.properties.ContractsCoordinatorComponent.m_TrackedContractItemUid',
		'.components.FItemCraftingComponent.*.RequestsQueue.*.InstigatorActorId',
		'.stats.FSinkchartsStats.*.CreatorPlayerId',
		'.components.FItemCraftingComponent.*.RequestsQueue.*.IngredientAllocations.*.ItemAllocNodes.*.AllocatedItems.*.ItemUniqueId', -- TODO [DA-4712]: Remove once this item loss is fixed
		'.stats.FBuildingBlueprintItemStats.*.PlayerBlueprintId', -- TODO [DA-4721]: Remove once this item loss is fixed
		'.properties.BP_TransportOrnithopter_CHOAM_C.m_HarnessedVehicleId',
		'.stats.FReferenceItemStats.*.ReferenceDatabaseId', -- These often point to items that no longer exist
		'.components.FTotemLandclaimComponent.*.m_PendingStakingUnitsEntityIds.*', -- Staking units are not transferred with the base backup. This is missing a cleanup
		'.components.FTotemLandclaimComponent.*.m_PendingVerticalStakingUnitsEntityIds.*' -- Staking units are not transferred with the base backup. This is missing a cleanup
	]::text[]));
$function$

@@@ENDRTN@@@
@@@RTN@@@58105
f
_character_transfer_replace_local_id_with_transfer_id
data text, path text
text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_replace_local_id_with_transfer_id(data text, path text)
 RETURNS text
 LANGUAGE plpgsql
AS $function$
declare
	v_kind _CharacterTransferEntryKind;
	v_raw_kind Text;
	v_id BigInt;
	v_transfer_id BigInt;
begin
	v_raw_kind = substring(data from 3 for 3);
	begin
		v_kind := v_raw_kind::_CharacterTransferEntryKind;
	exception
		when invalid_text_representation then
			raise exception 'sbPH2$ - Invalid transfer id in data "%" at "%", unknown kind: %', data, path, v_raw_kind;
	end;

	v_id := (substring(data from 7))::BigInt;
	if v_id = 0 then
		return format('!!%s@0', v_kind::text);
	end if;

	if v_id < 0 and v_id >= -2147483648 then
		v_id := v_id + 4294967296;
	end if;


	v_transfer_id := (select transfer_id from pg_temp.export_data where id=v_id and kind=v_kind);
	if v_transfer_id is null then
		if _character_transfer_property_not_exported_is_expected(path) then
			return format('!!%s@0', v_kind::text);
		else
			raise exception 'sbQ73$ - Id % by % was not exported', data, path;
		end if;
	end if;

	return format('!!%s@%s', v_kind::text, v_transfer_id::text);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58106
f
_character_transfer_replace_local_id_with_transfer_id_in_json
data jsonb, path text
jsonb

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_replace_local_id_with_transfer_id_in_json(data jsonb, path text)
 RETURNS jsonb
 LANGUAGE sql
AS $function$
	select case
		when data is json array then
			(select coalesce(jsonb_agg(_character_transfer_replace_local_id_with_transfer_id_in_json(
				element, path || '.*'
			)), '[]'::jsonb) from jsonb_array_elements(data) as element)
		when data is json object then
			(select coalesce(jsonb_object_agg(key, _character_transfer_replace_local_id_with_transfer_id_in_json(
				value, path || '.' || key
			)), '{}'::jsonb) from jsonb_each(data))
		when data is json scalar and jsonb_typeof(data) = 'string' and (data #>> '{}') like '!!___#%' then
			(select to_jsonb(_character_transfer_replace_local_id_with_transfer_id(
				data #>> '{}', path
			)))
		else
			data
	end;
$function$

@@@ENDRTN@@@
@@@RTN@@@58107
f
_character_transfer_replace_transfer_id_with_local_id
data text, path text
text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_replace_transfer_id_with_local_id(data text, path text)
 RETURNS text
 LANGUAGE plpgsql
AS $function$
declare
	v_kind _CharacterTransferEntryKind;
	v_id BigInt;
	v_transfer_id BigInt;
begin
	if substring(data from 6 for 1) != '@' then
		raise exception 'sbFM3$ - Invalid transfer id % at %: expected @ separator', data, path;
	end if;

	v_kind := substring(data from 3 for 3)::_CharacterTransferEntryKind;

	v_transfer_id := (substring(data from 7))::BigInt;
	if v_transfer_id = 0 then
		return format('!!%s#0', v_kind::text);
	end if;

	v_id := (select id from pg_temp.export_data where transfer_id=v_transfer_id and kind=v_kind);
	if v_id is null then
		raise exception 'sbQ64$ - Transfer id % at % was not imported', data, path;
	end if;

	return format('!!%s#%s', v_kind::text, v_id::text);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58108
f
_character_transfer_replace_transfer_id_with_local_id_in_json
data jsonb, path text
jsonb

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_replace_transfer_id_with_local_id_in_json(data jsonb, path text)
 RETURNS jsonb
 LANGUAGE plpgsql
AS $function$
declare
	v_id_str Text;
begin
	if data is json array then
		return (
			select coalesce(jsonb_agg(_character_transfer_replace_transfer_id_with_local_id_in_json(
				element, path || '.*'
			)), '[]'::jsonb) from jsonb_array_elements(data) as element
		);
	end if;

	if data is json object then
		return (
			select coalesce(jsonb_object_agg(key, _character_transfer_replace_transfer_id_with_local_id_in_json(
				value, path || '.' || key
			)), '{}'::jsonb) from jsonb_each(data)
		);
	end if;

	if data is json scalar then
		if jsonb_typeof(data) = 'string' then
			v_id_str := (data #>> '{}');

			if v_id_str like '!!___#%' then
				raise exception 'sbJ34$ - Non-transfer id % is found at %', v_id_str, path;
			end if;

			if v_id_str like '!!___@%' then
				return (select to_jsonb(_character_transfer_replace_transfer_id_with_local_id(v_id_str, path)));
			end if;

			if v_id_str = '!!@0' then
				raise exception 'sb5G3$ - Transfer id missing kind marker at %: %', path, v_id_str;
			end if;
		end if;
	end if;

	return data;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58109
f
_character_transfer_store_in_world_owned_vehicles_into_recovery
in_player_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_store_in_world_owned_vehicles_into_recovery(in_player_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    to_store RECORD;
BEGIN
	-- remove any vehicles in 'Travel' state, so that they get re-added as recovered vehicles
	DELETE FROM actor_state ast
		WHERE ast.state = 'Travel'
		AND ast.actor_id IN (
			SELECT v.id
			FROM actors a
				INNER JOIN vehicles v ON a.id = v.id
				INNER JOIN permission_actor p ON a.id = p.actor_id
				INNER JOIN permission_actor_rank r ON p.actor_id = r.permission_actor_id
			WHERE r.player_id = in_player_id
			AND r.rank = 1::smallint
		);

    FOR to_store IN
		SELECT v.id AS vehicle_id
		FROM actors a
			INNER JOIN vehicles v ON a.id = v.id
			INNER JOIN permission_actor p ON a.id = p.actor_id
			INNER JOIN permission_actor_rank r ON p.actor_id = r.permission_actor_id
		WHERE r.player_id = in_player_id
		AND r.rank = 1::smallint
		AND NOT EXISTS
		(
			SELECT 1 FROM actor_state
			WHERE actor_state.actor_id = v.id
			AND actor_state.state IS DISTINCT FROM 'Default'
		)
	LOOP
		-- note: storing hardcoded chassis durability because its not available from pure database :(
		-- this will only be shown incorrectly in UI though, the spawned vehicle will get the correct value
        PERFORM store_recovered_vehicle(to_store.vehicle_id, 1.0, 'None', true);

		-- we delete the permissions for this vehicle so that they don't get exported and cause duplicated key failures
        -- when the player tries to recover their vehicle on the target battlegroup
		-- note: the server will destroy the vehicle as soon as it gets the pg_notify emitted from the store_recovered_vehicle call above, so this would happen regardless
		DELETE FROM permission_actor pa WHERE pa.actor_id = to_store.vehicle_id;
    END LOOP;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58110
f
_character_transfer_top_level_export
in_kind dune._charactertransferentrykind, data jsonb
jsonb

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_top_level_export(in_kind dune._charactertransferentrykind, data jsonb)
 RETURNS jsonb
 LANGUAGE plpgsql
AS $function$
declare
    v_id BigInt;
	v_transfer_id BigInt;
    v_ref _CharacterTransferDataFilterRef;
	v_filter _CharacterTransferDataFilter;
begin
	v_filter := _character_transfer_get_filter(in_kind);
	data := data - (v_filter).removed;
	if (v_filter).id is not null then
		data := data - (v_filter).id;
	end if;
    foreach v_ref in array (v_filter).refs loop
        v_id := (data->>((v_ref).key))::BigInt;

        if v_id is null then
			if (v_ref).required then
	            raise exception 'sbP23$ - Required reference % not found in %', v_ref, data;
			else
				continue;
			end if;
        end if;

		v_transfer_id := (select transfer_id from pg_temp.export_data where kind=(v_ref).kind and id=v_id);
		if v_transfer_id is null then
			raise exception 'sbJG2$ - Id % in % not mapped into transfer id of kind %. Id exists as kind: %',
				v_id, data, (v_ref).kind, (select kind from pg_temp.export_data where id=v_id);
		end if;

        data := jsonb_set(data, array[(v_ref).key], to_jsonb(v_transfer_id));
    end loop;
    return data;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58111
f
_character_transfer_top_level_import
in_kind dune._charactertransferentrykind, data jsonb, in_id bigint
jsonb

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._character_transfer_top_level_import(in_kind dune._charactertransferentrykind, data jsonb, in_id bigint)
 RETURNS jsonb
 LANGUAGE plpgsql
AS $function$
declare
    v_ref_transfer_id BigInt;
	v_ref_id BigInt;
    v_ref _CharacterTransferDataFilterRef;
	v_filter _CharacterTransferDataFilter;
begin
	v_filter := _character_transfer_get_filter(in_kind);

	if (v_filter).id is null then
		if in_id is not null then
			raise exception 'sbGM2$ - Transfer logic error: not-null in_id for secondary kind: %, data: %', in_kind, data;
		end if;
	else
		if in_id is null then
			raise exception 'sb8V2$ - Transfer logic error: null in_id for primary kind: %, data: %', in_kind, data;
		end if;
		data := data || jsonb_build_object((v_filter).id, in_id);
	end if;

    foreach v_ref in array (v_filter).refs loop
        v_ref_transfer_id := (data->>((v_ref).key))::BigInt;
        if v_ref_transfer_id is null then
			if (v_ref).required then
	            raise exception 'sb2C2$ - Missing reference % in import of kind %: %', (v_ref).key, in_kind, data;
			else
				continue;
			end if;
        end if;
		v_ref_id := (select to_jsonb(id) from pg_temp.export_data where kind=(v_ref).kind and transfer_id=v_ref_transfer_id);
		if v_ref_id is null then
			raise exception 'sb3W3$ - Unknown reference %s with transfer id % in import for kind %, reference id %: %', (v_ref).key, v_ref_transfer_id, in_kind, v_ref_id, data;
		end if;
        data := jsonb_set(data, array[(v_ref).key], to_jsonb(v_ref_id));
    end loop;
    return data;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58112
f
_placeable_validate_totem_owner_id
in_totem_owner_id bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._placeable_validate_totem_owner_id(in_totem_owner_id bigint)
 RETURNS bigint
 LANGUAGE sql
BEGIN ATOMIC
 SELECT
         CASE
             WHEN (in_totem_owner_id = 0) THEN NULL::bigint
             WHEN (EXISTS ( SELECT 1
                FROM dune.fgl_entities
               WHERE (fgl_entities.entity_id = _placeable_validate_totem_owner_id.in_totem_owner_id))) THEN in_totem_owner_id
             ELSE NULL::bigint
         END AS "case";
END

@@@ENDRTN@@@
@@@RTN@@@58113
f
_user_data_encryption_initially_encrypt_existing_data

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._user_data_encryption_initially_encrypt_existing_data()
 RETURNS void
 LANGUAGE sql
AS $function$
	update encrypted_accounts set encrypted_funcom_id=encrypt_user_data(convert_from(encrypted_funcom_id, 'utf8'));
	update encrypted_player_state set
		encrypted_character_name=encrypt_user_data(convert_from(encrypted_character_name, 'utf8'));
$function$

@@@ENDRTN@@@
@@@RTN@@@58114
f
_user_data_encryption_setup_enabled
key_hash bytea
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._user_data_encryption_setup_enabled(key_hash bytea)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	if key_hash is null then
		raise exception 'User-data encryption requested but the server does not have the encryption key set';
	end if;

	if (select get_stored_user_data_encryption_taint_xmax()) is not null then
		-- should have been filtered by the main setup function
		raise exception 'Trying to enable encryption on a tainted data';
	end if;

	execute format($x$
		create or replace function get_stored_user_data_encryption_key_hash() returns bytea immutable
    		as $y$select '%s'::bytea;$y$ language sql;
	$x$, key_hash::text);

    -- the data is encrypted and we have the key
	create or replace function get_stored_user_data_encryption_status() returns UserDataEncryptionStatus immutable
		as $x$select 'Enabled'::UserDataEncryptionStatus;$x$ language sql;

	-- We may bake the key into the function code but then dumping the functions will reveal the key
	create or replace function encrypt_user_data(in_data text) returns bytea immutable as $x$
		select ext.encrypt(convert_to(in_data, 'utf8'), current_setting('funcom.user_data_encryption_key')::bytea, 'aes')::bytea;
	$x$ language sql;

	create or replace function decrypt_user_data(in_encrypted_data bytea) returns text immutable as $x$
		select convert_from(
			ext.decrypt(in_encrypted_data, current_setting('funcom.user_data_encryption_key')::bytea, 'aes'), 'utf8'
		);
	$x$ language sql;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58115
f
_user_data_encryption_setup_tainted

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune._user_data_encryption_setup_tainted()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	-- we not replacing get_stored_user_data_encryption_key_hash()
	if (select get_stored_user_data_encryption_key_hash()) is null then
		-- should have been filtered by the main setup function
		raise exception 'Tainted but the data is not encrypted in the first place';
	end if;

	execute format($x$
		create or replace function get_stored_user_data_encryption_taint_xmax() returns int8 immutable
    		as 'select %s;' language sql;
	$x$, pg_current_xact_id());

	-- the data is encrypted but we don't have the key (or don't want to)
	create or replace function get_stored_user_data_encryption_status() returns UserDataEncryptionStatus immutable
		as $x$select 'Tainted'::UserDataEncryptionStatus;$x$ language sql;

	create or replace function encrypt_user_data(in_data text) returns bytea immutable
		as $x$select convert_to(in_data, 'utf8')$x$ language sql;

	-- We may use the taint xmax to differentiate between encrypted/unencrypted data for that we would have to pass the
	-- xmin into the function (which is xid and not xid8)
	create or replace function decrypt_user_data(in_encrypted_data bytea) returns text immutable
		as $x$select encode(in_encrypted_data, 'hex');$x$ language sql;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58116
f
accept_guild_invite
in_invite_id bigint, in_role_id smallint, in_max_guild_count_per_player integer, in_max_members_per_guild integer, in_neutral_faction_id smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.accept_guild_invite(in_invite_id bigint, in_role_id smallint, in_max_guild_count_per_player integer, in_max_members_per_guild integer, in_neutral_faction_id smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	member_count INTEGER := 0;
	player_id BIGINT := 0;
	found_guild_id BIGINT := 0;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	-- check if invite exists
	SELECT guild_invites.player_id, guild_invites.guild_id FROM guild_invites WHERE invite_id = in_invite_id INTO player_id, found_guild_id;
	IF NOT FOUND THEN
    	RAISE EXCEPTION 'Trying to accept non exiting invite %.', in_invite_id;
	END IF;

	-- delete invite
	DELETE FROM guild_invites WHERE invite_id = in_invite_id;

	SELECT INTO member_count COUNT(*) FROM guild_members where guild_members.guild_id = found_guild_id;

	-- check if we've reached guild member limit
	IF member_count >= in_max_members_per_guild THEN
		RAISE EXCEPTION 'Cannot insert more than % members per guild.', in_max_members_per_guild;
	END IF;

	-- add member
	PERFORM add_guild_member(player_id, found_guild_id, in_role_id, in_max_guild_count_per_player, in_max_members_per_guild, in_neutral_faction_id);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58117
f
accept_party_invite
in_invite_id bigint, in_platform_session_id text, in_max_party_member_count integer
dune.partyacceptinviteresult

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.accept_party_invite(in_invite_id bigint, in_platform_session_id text, in_max_party_member_count integer)
 RETURNS dune.partyacceptinviteresult
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_player_id BIGINT;
	out_sender_id BIGINT;
	out_party_id BIGINT;
	out_player_platform_name TEXT;
	out_player_platform_id TEXT;
	out_sender_platform_name TEXT;
	out_sender_platform_session_id TEXT;
	out_accept_error PartyAcceptInviteResult DEFAULT 'Success'::PartyAcceptInviteResult;
BEGIN
	PERFORM parties_get_exclusive_operation_lock();

	-- check if invite exists
	SELECT party_id, player_id, sender_player_id, sender_platform_name, sender_platform_session_id FROM party_invites 
	WHERE invite_id = in_invite_id INTO out_party_id, out_player_id, out_sender_id, out_sender_platform_name, out_sender_platform_session_id;
	IF NOT FOUND THEN
		RAISE EXCEPTION 'Trying to accept non exiting party invite %.', in_invite_id;
		out_accept_error = 'NonExistingInvite'::PartyAcceptInviteResult;
	END IF;
	
	-- query receiver platform data
	SELECT acc.platform_name, acc.platform_id INTO out_player_platform_name, out_player_platform_id
	FROM accounts acc LEFT JOIN player_state ps ON acc.id=ps.account_id
	WHERE ps.player_controller_id = out_player_id;

	IF out_party_id IS NULL THEN
		PERFORM internal_create_party(in_invite_id, out_sender_id, out_sender_platform_session_id, out_sender_platform_name, 
			out_player_id, in_platform_session_id, out_player_platform_name);
	ELSE
		out_accept_error := internal_add_party_member(in_invite_id, out_party_id, out_player_id, in_platform_session_id, out_player_platform_name, in_max_party_member_count);
	END IF;

	-- Delete any sent or reiceived invites from the player who's accepting the invitation
	DELETE FROM party_invites WHERE sender_player_id = out_player_id OR player_id = out_player_id;
	RETURN out_accept_error;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58118
f
add_actor_audit
in_id bigint, in_class text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_actor_audit(in_id bigint, in_class text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO actor_audit("id", "class") VALUES(in_id, in_class) ON CONFLICT(id) DO NOTHING;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58119
f
add_event_log_data
in_game_event_owner bigint, in_universe_time bigint, in_map_name text, in_partition_id bigint, in_event_type integer, in_x_location double precision, in_y_location double precision, in_z_location double precision, in_is_player_facing boolean, in_custom_data text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_event_log_data(in_game_event_owner bigint, in_universe_time bigint, in_map_name text, in_partition_id bigint, in_event_type integer, in_x_location double precision, in_y_location double precision, in_z_location double precision, in_is_player_facing boolean, in_custom_data text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO game_events(actor_id, universe_time, map, partition_id, event_type, x, y, z, custom_data, player_facing_event) VALUES(in_game_event_owner, to_timestamp(in_universe_time), in_map_name, in_partition_id, in_event_type, in_x_location, in_y_location, in_z_location, in_custom_data::JsonB, in_is_player_facing);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58120
f
add_event_log_data_batched
in_data dune.eventlogbulkentrydata[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_event_log_data_batched(in_data dune.eventlogbulkentrydata[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	data_entry record = NULL;
BEGIN
	FOREACH data_entry IN ARRAY in_data
	LOOP
		PERFORM add_event_log_data(data_entry.game_event_owner, data_entry.universe_time, data_entry.map_name, data_entry.partition_id, data_entry.event_type,
			data_entry.x_location, data_entry.y_location, data_entry.z_location, data_entry.player_facing_event, data_entry.custom_args);
	END LOOP;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58121
f
add_guild_invite
in_player_id bigint, in_guild_id bigint, in_sender_player_id bigint, in_invite_sent_timespan bigint, in_max_guild_invites_per_guild integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_guild_invite(in_player_id bigint, in_guild_id bigint, in_sender_player_id bigint, in_invite_sent_timespan bigint, in_max_guild_invites_per_guild integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	invite_count INTEGER := 0;
	out_invite_id INTEGER;
	out_guild_name TEXT;
	out_guild_description TEXT;
	out_player_name TEXT;
	out_sender_name TEXT;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	-- check if guild exists
	SELECT guild_name, guild_description INTO out_guild_name, out_guild_description FROM guilds WHERE guild_id = in_guild_id;
	IF NOT FOUND THEN
    	RAISE EXCEPTION 'Trying to add invite to non existing guild %.', in_guild_id;
	END IF;

	-- check if we've reached the invite limit
	SELECT INTO invite_count COUNT(*) FROM guild_invites WHERE guild_invites.guild_id = in_guild_id;
	IF invite_count >= in_max_guild_invites_per_guild THEN
		RAISE EXCEPTION 'Cannot insert more than % guild invites per guild.', in_max_guild_invites_per_guild;
	END IF;

	-- check if this player already has an invite to this guild
	IF EXISTS (SELECT FROM guild_invites WHERE guild_id = in_guild_id AND player_id = in_player_id) THEN
		RAISE EXCEPTION 'Trying to add invite to a player that already has an invite to this guild.';
	END IF;

	-- add invite
	INSERT INTO guild_invites("guild_id", "player_id", "sender_player_id", "invite_sent_timespan") VALUES(in_guild_id, in_player_id, in_sender_player_id, in_invite_sent_timespan) RETURNING "invite_id" INTO out_invite_id;

	SELECT player_state.character_name INTO out_player_name
	FROM player_state
	WHERE player_state.player_controller_id = in_player_id;

	SELECT player_state.character_name INTO out_sender_name
	FROM player_state
	WHERE player_state.player_controller_id = in_sender_player_id;

	PERFORM pg_notify('guild_notify_channel', format(
	'add_invite#{"InviteId" : %s, "PlayerId" : %s , "GuildId" : %s, "GuildName" : "%s", "PlayerName" : "%s", "GuildDescription" : "%s", "SenderPlayerId" : %s, "SenderPlayerName" : "%s", "InviteSentUniverseTime" : %s}',
	 out_invite_id, in_player_id, in_guild_id, out_guild_name, out_player_name, out_guild_description, in_sender_player_id, out_sender_name, in_invite_sent_timespan));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58122
f
add_guild_member
in_player_id bigint, in_guild_id bigint, in_role_id smallint, in_max_guild_count_per_player integer, in_max_members_per_guild integer, in_neutral_faction_id smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_guild_member(in_player_id bigint, in_guild_id bigint, in_role_id smallint, in_max_guild_count_per_player integer, in_max_members_per_guild integer, in_neutral_faction_id smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	guild_count INTEGER := 0;
	should_clear_invites SMALLINT := 0;
	player_faction_id SMALLINT;
	guild_record record;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	-- we need to check if the player is already part of max amount of guilds before being able to add them
    SELECT INTO guild_count COUNT(*) FROM guild_members WHERE player_id = in_player_id;
    IF guild_count >= in_max_guild_count_per_player THEN
        RAISE EXCEPTION 'Cannot insert more than % guild entries for each user.', in_max_guild_count_per_player;
    END IF;

	SELECT * INTO guild_record
	FROM guilds
	WHERE guild_id = in_guild_id;

	-- check if guild exists
	IF guild_record IS NULL THEN
    	RAISE EXCEPTION 'Trying to add user to non existing guild %.', in_guild_id;
	END IF;

	player_faction_id := get_player_faction(in_player_id, in_neutral_faction_id);

	IF  player_faction_id != in_neutral_faction_id AND guild_record.guild_faction != in_neutral_faction_id AND player_faction_id != guild_record.guild_faction THEN
		RAISE EXCEPTION 'Trying to add user to with non compatible. player faction: %, guild faction: %', player_faction_id, guild_record.guild_faction;
	END IF;

	IF (SELECT COUNT(*) FROM guild_members where guild_members.guild_id = in_guild_id) = in_max_members_per_guild - 1 THEN
		should_clear_invites := 1;
	END IF;

	-- insert member
	INSERT INTO guild_members("player_id", "guild_id", "role_id") VALUES(in_player_id, in_guild_id, in_role_id);

	-- delete invite
    IF should_clear_invites = 1 THEN
        DELETE FROM guild_invites WHERE guild_id = in_guild_id;
    ELSE
	    DELETE FROM guild_invites WHERE guild_id = in_guild_id AND player_id = in_player_id;
    END IF;

	PERFORM pg_notify('guild_notify_channel', format('add_player#{"PlayerId" : %s , "PlayerFactionId" : %s, "GuildId" : %s, "RoleId" : %s, "ShouldClearInvites" : %s}', in_player_id, player_faction_id, in_guild_id, in_role_id, should_clear_invites));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58123
f
add_landclaim_segment
in_totem_id bigint, in_grid_location_x bigint, in_grid_location_y bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_landclaim_segment(in_totem_id bigint, in_grid_location_x bigint, in_grid_location_y bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO landclaim_segments(totem_id, grid_location_x, grid_location_y)
    VALUES(in_totem_id, in_grid_location_x, in_grid_location_y);
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58124
f
add_map_areas_surveyed_items
in_account_id bigint, in_area_id smallint, in_survey_point_marker_id bigint, in_surveyed_items_target jsonb, in_surveyed_items_progress jsonb, in_map_name text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_map_areas_surveyed_items(in_account_id bigint, in_area_id smallint, in_survey_point_marker_id bigint, in_surveyed_items_target jsonb, in_surveyed_items_progress jsonb, in_map_name text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO map_areas("account_id", "area_id", "survey_point_marker_id", "items_surveyed_target", "items_surveyed_progress", "map_name")
        VALUES(in_account_id, in_area_id, in_survey_point_marker_id, in_surveyed_items_target, in_surveyed_items_progress, in_map_name)
        ON CONFLICT ("account_id", "area_id", "map_name") DO UPDATE SET "survey_point_marker_id" = in_survey_point_marker_id, "items_surveyed_target" = in_surveyed_items_target, "items_surveyed_progress" = in_surveyed_items_progress
        WHERE map_areas.account_id = in_account_id AND map_areas.area_id = in_area_id and map_areas.map_name = in_map_name;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58125
f
add_map_areas_time_discovered
in_account_id bigint, in_area_id smallint, in_time_discovered timestamp without time zone, in_map_name text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_map_areas_time_discovered(in_account_id bigint, in_area_id smallint, in_time_discovered timestamp without time zone, in_map_name text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO map_areas("account_id", "area_id", "time_discovered", "map_name")
        VALUES(in_account_id, in_area_id, in_time_discovered, in_map_name)
        ON CONFLICT ("account_id", "area_id", "map_name") DO UPDATE SET "time_discovered" = in_time_discovered
        WHERE map_areas.account_id = in_account_id AND map_areas.area_id = in_area_id and map_areas.map_name = in_map_name;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58126
f
add_map_areas_time_first_entered
in_account_id bigint, in_area_id smallint, in_time_first_entered timestamp without time zone, in_map_name text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_map_areas_time_first_entered(in_account_id bigint, in_area_id smallint, in_time_first_entered timestamp without time zone, in_map_name text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO map_areas("account_id", "area_id", "time_first_entered", "map_name")
        VALUES(in_account_id, in_area_id, in_time_first_entered, in_map_name)
        ON CONFLICT ("account_id", "area_id", "map_name") DO UPDATE SET "time_first_entered" = in_time_first_entered
        WHERE map_areas.account_id = in_account_id AND map_areas.area_id = in_area_id and map_areas.map_name = in_map_name;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58127
f
add_partition_unique
in_map text, in_definition jsonb, in_dimension bigint, in_label text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_partition_unique(in_map text, in_definition jsonb, in_dimension bigint, in_label text DEFAULT NULL::text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	v_partition_id bigint;
BEGIN
	-- Don't use a constraint right now, this is only a dev-only helper function.
	-- We could add a constraint, but would need to check the performance of constraint on the jsonb field.
	insert into world_partition (map, partition_definition, dimension_index, label)
		select in_map, in_definition, in_dimension, in_label
	where not exists (
		select 1 from world_partition where map = in_map and partition_definition = in_definition and dimension_index = in_dimension
	) returning partition_id into v_partition_id;
	return v_partition_id;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58128
f
add_party_invite
in_sender_player_id bigint, in_sender_platform_name text, in_sender_platform_session_id text, in_player_id bigint, in_max_party_member_count integer, in_invite_sent_timespan bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.add_party_invite(in_sender_player_id bigint, in_sender_platform_name text, in_sender_platform_session_id text, in_player_id bigint, in_max_party_member_count integer, in_invite_sent_timespan bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_already_in_party BOOLEAN;
	out_already_invited BOOLEAN;
	out_sender_existing_party_id BIGINT;
	out_sender_party_count INTEGER;
	out_invite_id BIGINT;
	out_player_name TEXT;
	out_sender_name TEXT;
BEGIN

	-- check if invited player is already invited
	SELECT INTO out_already_invited EXISTS (SELECT 1 FROM party_invites where player_id = in_player_id AND sender_player_id = in_sender_player_id);
	IF out_already_invited THEN
		RAISE EXCEPTION 'The player % already has an invite from %.', in_player_id, in_sender_player_id;
	END IF;

	-- check if the sender party is full
	SELECT party_id INTO out_sender_existing_party_id from parties where party_leader_id = in_sender_player_id;
	SELECT INTO out_sender_party_count COUNT(*) FROM party_members WHERE party_id = out_sender_existing_party_id;

	IF out_sender_existing_party_id IS NOT NULL AND out_sender_party_count >= in_max_party_member_count THEN
		RAISE EXCEPTION 'Trying to invite player % for a party id % that is full.', in_player_id, out_sender_existing_party_id;
	END IF;

	-- add invite
	INSERT INTO party_invites("player_id", "party_id", "sender_player_id", "sender_platform_name", "sender_platform_session_id", "invite_sent_timespan") 
		VALUES(in_player_id, out_sender_existing_party_id, in_sender_player_id, in_sender_platform_name, in_sender_platform_session_id, in_invite_sent_timespan) RETURNING "invite_id" INTO out_invite_id;

	SELECT player_state.character_name INTO out_player_name
		FROM player_state WHERE player_state.player_controller_id = in_player_id;

	SELECT player_state.character_name INTO out_sender_name
		FROM player_state WHERE player_state.player_controller_id = in_sender_player_id;

	PERFORM pg_notify('party_notify_channel', format(
		'add_invite#{"InviteId" : %s, "SenderId" : %s, "SenderName" : "%s", "PlayerId" : %s , "PlayerName" : "%s", "InviteSentUniverseTime" : %s}',
		out_invite_id, in_sender_player_id, out_sender_name, in_player_id, out_player_name, in_invite_sent_timespan));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58129
f
adjust_player_virtual_currency_balance
in_controller_id bigint, in_currency_id smallint, in_delta bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.adjust_player_virtual_currency_balance(in_controller_id bigint, in_currency_id smallint, in_delta bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	current_balance BIGINT;
	current_delta BIGINT;
	new_delta BIGINT;
	fls_id TEXT;
	function_oid oid;
BEGIN
	SELECT INTO current_balance balance from player_virtual_currency_balances WHERE player_controller_id = in_controller_id AND currency_id = in_currency_id;
	INSERT INTO player_virtual_currency_balances("player_controller_id", "currency_id", "balance")
		VALUES (in_controller_id, in_currency_id, in_delta)
		ON CONFLICT (player_controller_id, currency_id) DO UPDATE SET balance = (player_virtual_currency_balances.balance + in_delta)
		RETURNING balance INTO current_balance;

    IF in_currency_id = get_solaris_id() THEN
	    GET DIAGNOSTICS function_oid = PG_ROUTINE_OID;
	    PERFORM log_event_solaris(function_oid, 'update_solaris', in_controller_id, current_balance, in_delta);
    END IF;

	current_delta = 0;
	IF current_balance < 0 THEN
		SELECT acc."user"
		INTO fls_id
		FROM accounts acc
		JOIN player_state ps on ps.account_id = acc.id
		WHERE ps.account_id = in_player_id
		LIMIT 1;

		PERFORM log_cheating(COALESCE(fls_id, in_player_id::text), 'negative_solaris');

		INSERT INTO player_virtual_currency_balances("player_controller_id", "currency_id", "balance")
			VALUES (in_controller_id, in_currency_id, 0)
			ON CONFLICT (player_controller_id, currency_id) DO UPDATE SET balance = 0;
		current_delta = current_balance;
	END IF;

	new_delta = in_delta + current_delta;
	RETURN new_delta;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58130
f
admin_get_character_details
in_account_id bigint
TABLE(account_id bigint, player_id text, character_name text, online_status text, last_avatar_activity timestamp with time zone, class text, map text, transform dune.transform, server_id text, partition_id bigint, partition_label text, dimension_index integer, gas_attributes jsonb, properties jsonb, slot_name text, fgl_data text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.admin_get_character_details(in_account_id bigint)
 RETURNS TABLE(account_id bigint, player_id text, character_name text, online_status text, last_avatar_activity timestamp with time zone, class text, map text, transform dune.transform, server_id text, partition_id bigint, partition_label text, dimension_index integer, gas_attributes jsonb, properties jsonb, slot_name text, fgl_data text)
 LANGUAGE plpgsql
AS $function$
begin
	return query SELECT player_state.account_id, accounts.user As player_id, player_state.character_name, CASE WHEN is_player_offline(accounts.user) THEN 'Offline' ELSE 'Online' END AS online_status, player_state.last_avatar_activity,
		actors.class, actors.map, actors.transform, player_state.server_id, actors.partition_id, world_partition.label, actors.dimension_index,
		actors.gas_attributes, actors.properties, actor_fgl_entities.slot_name,
		string_agg(cast(to_json((select d from (select fgl_entities.components) d)) as varchar), ', ') as fgl_data
	from fgl_entities
	left join actor_fgl_entities on fgl_entities.entity_id = actor_fgl_entities.entity_id
	left join actors on actor_fgl_entities.actor_id = actors.id
	left join player_state on player_state.player_pawn_id = actors.id
	left join accounts on accounts.id = player_state.account_id
	left join world_partition on world_partition.partition_id = actors.partition_id
	where player_state.account_id = in_account_id
		and actor_fgl_entities.slot_name = 'DuneCharacter'
	group by player_state.account_id, player_state.character_name, player_state.last_avatar_activity, accounts.user,
		actors.class, actors.map, actors.transform, player_state.server_id, actors.partition_id, world_partition.label, actors.dimension_index,
		actors.gas_attributes, actors.properties, actor_fgl_entities.slot_name;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58131
f
admin_get_character_ids
in_search_term text
TABLE(id bigint, "user" text, character_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.admin_get_character_ids(in_search_term text)
 RETURNS TABLE(id bigint, "user" text, character_name text)
 LANGUAGE plpgsql
AS $function$
begin
	return query
	select accounts.id, accounts.user, player_state.character_name
	from accounts
	left join player_state on player_state.account_id = accounts.id
	where lower(accounts.user) like in_search_term or lower(player_state.character_name) like in_search_term;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58132
f
admin_get_inventory_details
in_account_id bigint
TABLE(inventory_id bigint, item_id bigint, stack_size integer, template_id text, acquisition_time bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.admin_get_inventory_details(in_account_id bigint)
 RETURNS TABLE(inventory_id bigint, item_id bigint, stack_size integer, template_id text, acquisition_time bigint)
 LANGUAGE sql
AS $function$
	select
		inventories.id as inventory_id, items.id as item_id, items.stack_size, items.template_id, items.acquisition_time
	from
		items
	left join
		inventories on inventories.id = items.inventory_id
	left join
		player_state on inventories.actor_id = player_state.player_pawn_id
	where
		player_state.account_id = in_account_id
	order by
		template_id;
$function$

@@@ENDRTN@@@
@@@RTN@@@58133
f
admin_get_journey_details
in_player_id text, in_story_node_id text
TABLE(out_story_node_id text, out_override_reward_block boolean, out_has_pending_reward boolean, out_complete_condition_state jsonb, out_reveal_condition_state jsonb, out_fail_condition_state jsonb, out_metadata_state jsonb, out_reset_group dune.journeystoryresetgroup)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.admin_get_journey_details(in_player_id text, in_story_node_id text DEFAULT '%%'::text)
 RETURNS TABLE(out_story_node_id text, out_override_reward_block boolean, out_has_pending_reward boolean, out_complete_condition_state jsonb, out_reveal_condition_state jsonb, out_fail_condition_state jsonb, out_metadata_state jsonb, out_reset_group dune.journeystoryresetgroup)
 LANGUAGE sql
AS $function$
	select story_node_id, override_reward_block, has_pending_reward, complete_condition_state, reveal_condition_state, fail_condition_state, metadata_state, reset_group
	from journey_story_node
	where story_node_id like in_story_node_id
	and account_id in (
		select id
		from accounts a
		where a.user = in_player_id
	);
$function$

@@@ENDRTN@@@
@@@RTN@@@58134
f
admin_get_mnemonic_recall_details
in_account_id bigint
TABLE(mnemonic_recall_id bigint, lesson_id text, lesson_state bigint, lesson_progress integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.admin_get_mnemonic_recall_details(in_account_id bigint)
 RETURNS TABLE(mnemonic_recall_id bigint, lesson_id text, lesson_state bigint, lesson_progress integer)
 LANGUAGE sql
AS $function$
	select
		mnemonic_recall.id, lesson_id, lesson_state, lesson_progress
	from
		mnemonic_recall
	where
		mnemonic_recall.account_id = in_account_id
$function$

@@@ENDRTN@@@
@@@RTN@@@58135
f
admin_get_partitions

TABLE(out_partition_id bigint, out_server_id text, out_partition_definition jsonb, out_dimension_index integer, out_blocked boolean, out_label text, out_map text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.admin_get_partitions()
 RETURNS TABLE(out_partition_id bigint, out_server_id text, out_partition_definition jsonb, out_dimension_index integer, out_blocked boolean, out_label text, out_map text)
 LANGUAGE sql
AS $function$
	select * from load_partition_definition_map();
$function$

@@@ENDRTN@@@
@@@RTN@@@58136
f
admin_move_offline_player
in_fls_id text, in_target_partition_name text, in_target_location dune.vector
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.admin_move_offline_player(in_fls_id text, in_target_partition_name text, in_target_location dune.vector)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	if not (select is_player_offline(in_fls_id)) then
		raise exception 'Player must be Offline';
	end if;

	if not exists(select 1 from world_partition where label = in_target_partition_name) then
		raise exception 'Partition with name % not found', in_target_partition_name;
	end if;

	perform (with target_partition as (
		select partition_id, map, dimension_index
		from world_partition
		where label = in_target_partition_name
		limit 1
	)
	select admin_move_offline_player_to_partition(in_fls_id, target_partition.partition_id, in_target_location)
	from target_partition);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58137
f
admin_move_offline_player_to_partition
in_fls_id text, in_target_partition_id bigint, in_target_location dune.vector
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.admin_move_offline_player_to_partition(in_fls_id text, in_target_partition_id bigint, in_target_location dune.vector)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	if not (select is_player_offline(in_fls_id)) then
		-- CAVEAT: DirectorDbApi.TryMoveOfflinePlayerToPartition depends on this string.
		raise exception 'Player must be Offline';
	end if;

	if not exists(select 1 from world_partition where partition_id = in_target_partition_id) then
		raise exception 'Partition with ID % not found', in_target_partition_id;
	end if;

	raise notice 'Moving player % to partition % location x=%, y=%, z=%', in_fls_id, in_target_partition_id, in_target_location.x, in_target_location.y, in_target_location.z;

	with target_partition as (
		select partition_id, map, dimension_index
		from world_partition
		where partition_id = in_target_partition_id
		limit 1
	), target_pawn_id as (
		select player_state.player_pawn_id as id
		from accounts, player_state
		where accounts.user = in_fls_id and accounts.id = player_state.account_id
		limit 1
	), update_overmap_player_location as (
		select
			case 
		        when target_partition.map = 'Overmap' then overmap_save_player_survival_data(target_pawn_id.id, null, false, in_target_location)
		        else null
    		end
		from target_pawn_id, target_partition
	)
	update actors
		set
			transform = (in_target_location, (transform).rotation),
			map = upgrade_map_name(target_partition.map),
			dimension_index = target_partition.dimension_index,
			partition_id = target_partition.partition_id
		from
			target_pawn_id, target_partition, update_overmap_player_location
		where actors.id = target_pawn_id.id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58138
f
admin_read_player_tags
in_account_id bigint
TABLE(tags text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.admin_read_player_tags(in_account_id bigint)
 RETURNS TABLE(tags text)
 LANGUAGE sql
AS $function$
	select
		pt.tag
	from
		player_tags as pt
	where
		pt.account_id = in_account_id
$function$

@@@ENDRTN@@@
@@@RTN@@@58139
f
advance_items_id_sequencer
count bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.advance_items_id_sequencer(count bigint DEFAULT 1)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
  next_val  BIGINT;
  next_free BIGINT;
BEGIN
  IF count < 1 THEN
    RAISE EXCEPTION 'count must be >= 1';
  END IF;

  PERFORM pg_advisory_xact_lock(('items_id_seq'::regclass)::oid::bigint);
  
  next_val := nextval('items_id_seq'::regclass);

  -- next free id after reserving `count` ids starting at next_val
  next_free := next_val + count;

  -- make the next nextval() return next_free
  PERFORM setval('items_id_seq'::regclass, next_free, false);

  RETURN next_val;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58140
f
assign_actor_id
in_class text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.assign_actor_id(in_class text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
    new_id BIGINT;
BEGIN
	INSERT INTO actors(id) VALUES(DEFAULT) RETURNING id INTO new_id;
	PERFORM add_actor_audit(new_id, in_class);

	RETURN new_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58141
f
base_backup_delete
in_base_backup_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_delete(in_base_backup_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    actors_to_destroy BIGINT[];
BEGIN
    DELETE FROM actors a WHERE id = ANY(
        SELECT actor_id
        FROM base_backup_linked_actors bbla
        WHERE bbla.id = in_base_backup_id
    );

    DELETE FROM base_backups WHERE id = in_base_backup_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58142
f
base_backup_find_totems_from_player_owner
in_player_id bigint
TABLE(totem_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_find_totems_from_player_owner(in_player_id bigint)
 RETURNS TABLE(totem_id bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
        SELECT t.id
        FROM totems t
            JOIN permission_actor_rank par ON par.permission_actor_id = t.id
            WHERE par.player_id = in_player_id AND par.rank = 1;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58143
f
base_backup_finish_placing
in_base_backup_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_finish_placing(in_base_backup_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    WITH base_info AS (
        SELECT
            bb.id AS base_backup_id,
            a.partition_id,
            a.dimension_index,
            a.map
        FROM
            base_backups bb
            JOIN actors a on bb.player_id = a.id
        WHERE
            bb.id = in_base_backup_id
    )
    UPDATE actors
    SET
        partition_id = base_info.partition_id,
        dimension_index = base_info.dimension_index,
        map = base_info.map
    FROM
        base_backup_linked_actors bbl
        JOIN base_info ON bbl.id = base_info.base_backup_id
    WHERE
        actors.id = bbl.actor_id;

    DELETE FROM actor_state a
        WHERE actor_id = ANY(
            SELECT actor_id
            FROM base_backup_linked_actors bbla
            WHERE bbla.id = in_base_backup_id
        );

    DELETE FROM base_backups
        WHERE id = in_base_backup_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58144
f
base_backup_get_actors_to_spawn
in_base_backup_id bigint
SETOF dune.actorspawninfo

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_get_actors_to_spawn(in_base_backup_id bigint)
 RETURNS SETOF dune.actorspawninfo
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
		SELECT a.id, a.class as class_name, a.transform, a.partition_id, a.dimension_index
        FROM actors as a
        WHERE a.id IN (
            SELECT actor_id FROM base_backup_linked_actors as bbla WHERE bbla.id = in_base_backup_id
        );
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58145
f
base_backup_get_available_backups
in_player_id bigint
TABLE(id bigint, base_backup_name text, totem_id bigint, totem_buildable_type text, landclaim_original_global_location real[], base_backup_map text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_get_available_backups(in_player_id bigint)
 RETURNS TABLE(id bigint, base_backup_name text, totem_id bigint, totem_buildable_type text, landclaim_original_global_location real[], base_backup_map text)
 LANGUAGE plpgsql
AS $function$
begin
    RETURN QUERY
    SELECT
        bb.id,
        bb.base_backup_name,
        t.id AS totem_id,
        p.building_type,
        t.landclaim_original_global_location,
        a.map
    FROM base_backups bb
        JOIN base_backup_linked_actors bbla ON bbla.id = bb.id
        JOIN totems t ON bbla.actor_id = t.id
        JOIN actors a ON a.id = t.id
        JOIN placeables p ON p.id = a.id
    WHERE bb.player_id = in_player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58146
f
base_backup_get_buildable_data
in_base_backup_id bigint
TABLE(buildable_type text, total_count integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_get_buildable_data(in_base_backup_id bigint)
 RETURNS TABLE(buildable_type text, total_count integer)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT t.buildable_type, SUM(t.cnt)::INT AS total_count
    FROM (
        SELECT bi.building_type AS buildable_type, COUNT(*) AS cnt
        FROM base_backup_linked_actors bla
        JOIN building_instances bi ON bla.actor_id = bi.building_id
        WHERE
            bla.id = in_base_backup_id AND
            (bi.building_flags IS NULL OR (bi.building_flags & (1 << 2) = 0 AND bi.building_flags & (1 << 7) = 0)) -- flag 2 and 7 not enabled, which relates to holograms and extensions
        GROUP BY bi.building_type

        UNION ALL

        SELECT p.building_type AS buildable_type, COUNT(*) AS cnt
        FROM base_backup_linked_actors bla
        JOIN placeables p ON bla.actor_id = p.id
        WHERE bla.id = in_base_backup_id AND p.is_hologram = FALSE
        GROUP BY p.building_type
    ) t
    GROUP BY t.buildable_type;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58147
f
base_backup_get_data
in_base_backup_id bigint
dune.getbasebackupdata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_get_data(in_base_backup_id bigint)
 RETURNS dune.getbasebackupdata
 LANGUAGE plpgsql
AS $function$
DECLARE
    base_backup_name TEXT;
    totem_data BaseBackupTotemData;
    buildings_array BaseBackupBuildingItem[];
    placeables_array BaseBackupPlaceableItem[];
BEGIN

    SELECT bb.base_backup_name
        INTO base_backup_name
        FROM base_backups bb
        WHERE bb.id = in_base_backup_id;

    totem_data := base_backup_get_totem_data(in_base_backup_id);

    -- building pieces
    SELECT array_agg((bi.building_id, bi.instance_id, bi.building_type, bi.transform, bi.building_flags)::BaseBackupBuildingItem)
		into buildings_array
		FROM
            building_instances bi
            JOIN base_backup_linked_actors bbla ON bi.building_id = bbla.actor_id
		WHERE bbla.id = in_base_backup_id;

    -- placeables
    SELECT array_agg((p.building_type, a.transform)::BaseBackupPlaceableItem)
		into placeables_array
		FROM
            placeables p
            JOIN actors a ON p.id = a.id
            JOIN base_backup_linked_actors bbla ON a.id = bbla.actor_id
		WHERE
            bbla.id = in_base_backup_id;

    return ROW(base_backup_name, totem_data, buildings_array, placeables_array)::GetBaseBackupData;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58148
f
base_backup_get_totem_data
in_base_backup_id bigint
dune.basebackuptotemdata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_get_totem_data(in_base_backup_id bigint)
 RETURNS dune.basebackuptotemdata
 LANGUAGE plpgsql
AS $function$
DECLARE
    totem_id BIGINT;
    result BaseBackupTotemData;
BEGIN
    SELECT t.id
        INTO totem_id
        FROM totems t JOIN base_backup_linked_actors bbla ON t.id = bbla.actor_id
        WHERE bbla.id = in_base_backup_id
        LIMIT 1;

    IF totem_id IS NULL THEN
        RAISE EXCEPTION 'No totem found for base_backup id %', in_base_backup_id;
    END IF;

    result := base_backup_get_totem_data_from_totem_id(totem_id);

    RETURN result;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58149
f
base_backup_get_totem_data_from_totem_id
in_totem_id bigint
dune.basebackuptotemdata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_get_totem_data_from_totem_id(in_totem_id bigint)
 RETURNS dune.basebackuptotemdata
 LANGUAGE plpgsql
AS $function$
DECLARE
    result BaseBackupTotemData;
BEGIN
    SELECT
        t.id,
        p.building_type,
        a.map,
        t.landclaim_original_global_location,
        t.landclaim_original_global_yaw_rotation,
        t.landclaim_vertical_level
    INTO
        result.totem_actor_id,
        result.totem_building_type,
        result.totem_map,
        result.landclaim_original_global_location,
        result.landclaim_original_global_yaw_rotation,
        result.landclaim_vertical_level
    FROM totems t
        JOIN placeables p ON p.id = t.id
        JOIN actors a ON a.id = t.id
    WHERE t.id = in_totem_id
    LIMIT 1;

    IF result.totem_actor_id IS NULL THEN
        RAISE EXCEPTION 'No totem found for totem_id %', in_totem_id;
    END IF;

    SELECT array_agg(ROW(grid_location_x, grid_location_y)::SMALLINTPOINT)
        INTO result.landclaim_grid
        FROM landclaim_segments s
        WHERE s.totem_id = result.totem_actor_id;

    RETURN result;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58150
f
base_backup_get_totem_id
backup_id bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_get_totem_id(backup_id bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
    result BIGINT;
BEGIN
    SELECT t.id
        INTO result
        FROM totems t
            JOIN base_backup_linked_actors bbla ON t.id = bbla.actor_id
        WHERE bbla.id = backup_id
        LIMIT 1;

    IF result IS NULL THEN
        RAISE EXCEPTION 'No totem found for base_backup id %', backup_id;
    END IF;

    RETURN result;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58151
f
base_backup_recycle
in_base_backup_id bigint, in_target_inventory_id bigint
integer

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_recycle(in_base_backup_id bigint, in_target_inventory_id bigint)
 RETURNS integer
 LANGUAGE plpgsql
AS $function$
DECLARE
    base_backup_items_moved INT;
BEGIN
    UPDATE items
    SET inventory_id = in_target_inventory_id
    FROM
        inventories inv
        JOIN base_backup_linked_actors bbla ON inv.actor_id = bbla.actor_id
    WHERE
        items.inventory_id = inv.id
        AND bbla.id = in_base_backup_id;

    get diagnostics base_backup_items_moved = ROW_COUNT;

    -- Re-organize the index of all the items
    UPDATE items
    SET position_index = new_index
    FROM (
        SELECT
            id,
            ROW_NUMBER() OVER (ORDER BY position_index) - 1 AS new_index
        FROM items
        WHERE inventory_id = in_target_inventory_id
    ) AS sub
    WHERE items.id = sub.id;

    PERFORM base_backup_delete(in_base_backup_id);

    return base_backup_items_moved;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58152
f
base_backup_save
in_player_actor_id bigint, in_base_backup_name text, in_building_pieces_to_link dune.basebackupbuildingitem[], in_placeables_to_link bigint[], in_placeables_to_remove_totem_owner bigint[]
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_save(in_player_actor_id bigint, in_base_backup_name text, in_building_pieces_to_link dune.basebackupbuildingitem[], in_placeables_to_link bigint[], in_placeables_to_remove_totem_owner bigint[])
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_backup_id BIGINT;
    totem_id BIGINT;
BEGIN
    -- Find and Validate the Totem exists in the list
    SELECT t.id INTO totem_id
        FROM totems t
            JOIN unnest(in_placeables_to_link) AS ai(actor_id) ON t.id = ai.actor_id
        LIMIT 1;

    IF totem_id IS NULL THEN
        RAISE EXCEPTION 'No totem found for base_backup_save';
    END IF;

    INSERT INTO base_backups(player_id, base_backup_name)
        VALUES (in_player_actor_id, in_base_backup_name) RETURNING id INTO v_backup_id;

    -- for each building_id, create a new actor for those building pieces and then assign that new building_id to the building pieces.
    with
        input as (select DISTINCT building_id from unnest(in_building_pieces_to_link)),
        instances_input as (select building_id, instance_id from unnest(in_building_pieces_to_link)),
        new_actor_ids as (select nextval('actors_id_seq') as new_id, building_id as old_id from input),
        _copy_building_actors as (
            insert into actors("id", "class", "map", "transform", "partition_id", "dimension_index")
            select i.new_id, a."class", a."map", a."transform", a."partition_id", a."dimension_index"
            from new_actor_ids i join actors a on (i.old_id = a.id)),
        _insert_actor_states as (insert into actor_state(actor_id, state) select new_id, 'BaseBackup' from new_actor_ids),
        _insert_buildings as (insert into buildings("id") select new_id from new_actor_ids),
        _insert_base_backup_linked_actors as (insert into base_backup_linked_actors("id", "actor_id") select v_backup_id, new_id from new_actor_ids)
    update building_instances bi set building_id = ids.new_id from new_actor_ids ids join instances_input i on (ids.old_id = i.building_id) where bi.building_id = ids.old_id and bi.instance_id = i.instance_id;

    -- Link all placeables to the linked_base_backup_id and set them to BaseBackup ActorState in actor_state
    INSERT INTO base_backup_linked_actors(id, actor_id)
        SELECT v_backup_id, unnest(in_placeables_to_link);

    INSERT INTO actor_state(actor_id, state)
        SELECT unnest(in_placeables_to_link), 'BaseBackup'::ActorState;

    UPDATE placeables
        SET owner_entity_id = NULL
        WHERE id = ANY(in_placeables_to_remove_totem_owner);

    -- We need to remove permissions for the Totem
    PERFORM permission_actor_destroy(totem_id);

    -- Remove all invoices from the totem
    PERFORM taxation_remove_invoices_from_totem(totem_id);

    RETURN v_backup_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58153
f
base_backup_save_all_totems_from_player_owner
in_player_id bigint
TABLE(base_backup_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_save_all_totems_from_player_owner(in_player_id bigint)
 RETURNS TABLE(base_backup_id bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
        SELECT base_backup_save_from_totem(in_player_id, totem_id)
        FROM base_backup_find_totems_from_player_owner(in_player_id);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58154
f
base_backup_save_from_totem
in_player_id bigint, totem_id bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.base_backup_save_from_totem(in_player_id bigint, totem_id bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
    totem_entity_id BIGINT;
    totem_name TEXT;
    building_pieces_to_link BaseBackupBuildingItem[];
    placeables_to_link BIGINT[];
    placeables_to_remove_totem_owner BIGINT[];
BEGIN
    SELECT entity_id INTO totem_entity_id
        FROM actor_fgl_entities
        WHERE actor_id = totem_id;

    SELECT COALESCE(actor_name, '') INTO totem_name
        FROM permission_actor
        WHERE actor_id = totem_id
        LIMIT 1;

    SELECT array_agg((bi.building_id, bi.instance_id, bi.building_type, bi.transform, bi.building_flags)::BaseBackupBuildingItem)
        INTO building_pieces_to_link
        FROM building_instances bi
        WHERE bi.owner_entity_id = totem_entity_id;

    SELECT array_agg(p.id)
        INTO placeables_to_link
        FROM placeables p
        WHERE (p.owner_entity_id = totem_entity_id AND p.has_buildable_support = TRUE) OR p.id = totem_id;

    SELECT array_agg(p.id)
        INTO placeables_to_remove_totem_owner
        FROM placeables p
        WHERE p.owner_entity_id = totem_entity_id AND p.has_buildable_support = FALSE AND p.id != totem_id;

    RETURN base_backup_save(
        in_player_id,
        totem_name,
        building_pieces_to_link,
        placeables_to_link,
        placeables_to_remove_totem_owner
    );
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58155
f
break_guild_allegiance
in_guild_id bigint, in_neutral_faction_id smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.break_guild_allegiance(in_guild_id bigint, in_neutral_faction_id smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	guild_data_record record;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	SELECT * INTO guild_data_record FROM guilds WHERE guild_id = in_guild_id;
	IF guild_data_record IS NULL THEN
		RAISE EXCEPTION 'Trying to break guild allegiance of a non existing guild: %', in_guild_id;
	END IF;

	if guild_data_record.guild_faction = in_neutral_faction_id THEN
		RAISE EXCEPTION 'Guild already has neutral faction';
	END IF;

	UPDATE guilds SET guild_faction = in_neutral_faction_id WHERE guilds.guild_id = in_guild_id;

	PERFORM pg_notify('guild_notify_channel', format('break_guild_allegiance#{"GuildId" : %s , "OldGuildFactionDbId" : %s, "NewGuildFactionDbId" : %s}', in_guild_id, guild_data_record.guild_faction, in_neutral_faction_id));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58156
f
can_takeover_account
in_user_id text
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.can_takeover_account(in_user_id text)
 RETURNS boolean
 LANGUAGE sql
AS $function$
    select takeoverable from accounts WHERE "user" = in_user_id;
$function$

@@@ENDRTN@@@
@@@RTN@@@58157
f
change_player_faction
in_player_id bigint, in_faction_id smallint, neutral_faction_id smallint, in_utc_time_faction_change timestamp without time zone
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.change_player_faction(in_player_id bigint, in_faction_id smallint, neutral_faction_id smallint, in_utc_time_faction_change timestamp without time zone)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	found_role_id SMALLINT;
BEGIN

	if in_faction_id = neutral_faction_id THEN
		DELETE FROM player_faction WHERE actor_id = in_player_id;

		PERFORM pg_notify('faction_notify_channel', format('remove_player#{"PlayerId" : %s}', in_player_id));
	ELSE
		INSERT INTO player_faction(actor_id, faction_id, utc_time_faction_change) VALUES(in_player_id, in_faction_id, in_utc_time_faction_change) 
			ON CONFLICT (actor_id) DO UPDATE SET faction_id = in_faction_id, utc_time_faction_change = in_utc_time_faction_change;

		PERFORM pg_notify('faction_notify_channel', format('add_player#{"PlayerId" : %s, "FactionId" : %s}', in_player_id, in_faction_id));
	END IF;

	PERFORM handle_player_faction_guild_effects(in_player_id, in_faction_id, neutral_faction_id);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58158
f
character_migration_export
in_fls_id text
jsonb

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.character_migration_export(in_fls_id text)
 RETURNS jsonb
 LANGUAGE plpgsql
AS $function$
DECLARE
	v_player_controller_id BigInt;
BEGIN
	select out_player_controller_id
		into v_player_controller_id
	from _character_transfer_pre_export_validation(in_fls_id);

	perform _character_transfer_store_in_world_owned_vehicles_into_recovery(v_player_controller_id);
	perform base_backup_save_all_totems_from_player_owner(v_player_controller_id);
	-- Add any other migration-specific actions here before the export call

	return character_transfer_export(in_fls_id);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58159
f
character_migration_import
in_data jsonb, in_fls_id text, in_character_name text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.character_migration_import(in_data jsonb, in_fls_id text, in_character_name text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
BEGIN
	-- Add any migration-specific actions here before the import call
	RETURN character_transfer_import(in_data, in_fls_id, in_character_name);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58160
f
character_transfer_export
in_fls_id text
jsonb

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.character_transfer_export(in_fls_id text)
 RETURNS jsonb
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_acc_id BigInt;
    v_funcom_id Text;
	v_player_controller_id BigInt;
	v_player_pawn_id BigInt;
	v_vehicle_ids BigInt[];
BEGIN
	select out_acc_id, out_funcom_id, out_player_controller_id, out_player_pawn_id
		into v_acc_id, v_funcom_id, v_player_controller_id, v_player_pawn_id
	from _character_transfer_pre_export_validation(in_fls_id);

	perform _character_transfer_create_data_table();

	-- accounts

    insert into pg_temp.export_data("id", "kind", "data")
        select id, 'acc', _character_transfer_top_level_export('acc', to_jsonb(accounts))
			from accounts where accounts.id=v_acc_id;

	-- Vehicle and player actors

	insert into pg_temp.export_data("id", "kind", "data")
		select id, 'act', _character_transfer_top_level_export('act', to_jsonb(actors) - 'partition_id' #- '{properties,LandsraadCharacterComponent,m_NextContractAbandonUniverseTimeLimit}')
			from actors where id IN (
				-- player actors
            	select unnest(array[player_controller_id, player_state_id, player_pawn_id]) as id
					from player_state where account_id=v_acc_id
				union
				-- backup vehicles
				select vehicle_id as id from backup_vehicles where account_id=v_acc_id
				union
				-- recovered vehicles
				select vehicle_id as id from recovered_vehicles where account_id=v_acc_id
				union
				-- base backup actors
				select actor_id as id from base_backup_linked_actors where id IN (
					select id from base_backups where player_id=v_player_controller_id
				)
	        );

	insert into pg_temp.export_data("id", "kind", "data")
        select entity_id, 'fgl', _character_transfer_top_level_export(
			'fgl', to_jsonb(fgl_entities) || to_jsonb(actor_fgl_entities)
		) from actor_fgl_entities join fgl_entities using (entity_id) where actor_id IN (
			select id from pg_temp.export_data where kind='act'
		);


	-- Actor state entries tied to actors we export
	insert into pg_temp.export_data("id", "kind", "data")
	select actor_id, 'ActorState', _character_transfer_top_level_export('ActorState', to_jsonb(actor_state))
		from actor_state where
			actor_id IN (select id from pg_temp.export_data where kind = 'act');


	-- Permission data for the transferred actors
	insert into pg_temp.export_data("id", "kind", "data")
		select actor_id, 'PermissionActor', _character_transfer_top_level_export('PermissionActor', to_jsonb(permission_actor))
			from permission_actor where actor_id IN (select id from pg_temp.export_data where kind = 'act');
	insert into pg_temp.export_data("id", "kind", "data")
		select permission_actor_id, 'PermissionActorRank', _character_transfer_top_level_export('PermissionActorRank', to_jsonb(permission_actor_rank))
			from permission_actor_rank where
				permission_actor_id IN (select id from pg_temp.export_data where kind = 'act')
				and player_id IN (select id from pg_temp.export_data where kind = 'act');

	-- vehicles and vehicle modules

    insert into pg_temp.export_data("kind", "data")
        select 'Vehicle', _character_transfer_top_level_export('Vehicle', to_jsonb(vehicles))
			-- vehicle ids are aliases for actor ids
            from vehicles where id IN (select id from pg_temp.export_data where kind = 'act');

	insert into pg_temp.export_data("id", "kind", "data")
        select id, 'VehicleModule', _character_transfer_top_level_export('VehicleModule', to_jsonb(vehicle_modules))
			-- vehicle ids are aliases for actor ids
            from vehicle_modules where vehicle_id IN (select id from pg_temp.export_data where kind = 'act');

	insert into pg_temp.export_data("kind", "data")
		select 'BackupVehicle', _character_transfer_top_level_export('BackupVehicle', to_jsonb(backup_vehicles))
			from backup_vehicles where account_id=v_acc_id;

	insert into pg_temp.export_data("kind", "data")
		select 'RecoveredVehicle', _character_transfer_top_level_export('RecoveredVehicle', to_jsonb(recovered_vehicles))
			from recovered_vehicles where account_id=v_acc_id;

	-- inventories and items

    insert into pg_temp.export_data("id", "kind", "data")
        select id, 'inv', _character_transfer_top_level_export('inv', to_jsonb(inventories))
            from inventories where
				actor_id IN (select id from pg_temp.export_data where kind = 'act')
				or vehicle_module_id IN (select id from pg_temp.export_data where kind = 'VehicleModule');

    insert into pg_temp.export_data("id", "kind", "data")
        select id, 'itm', _character_transfer_top_level_export('itm', to_jsonb(items))
            from items where inventory_id IN (select id from pg_temp.export_data where kind = 'inv');

    insert into pg_temp.export_data("id", "kind", "data")
        select null, 'ActorInventory', _character_transfer_top_level_export('ActorInventory', to_jsonb(actor_inventories))
            from actor_inventories where inventory_id IN (select id from pg_temp.export_data where kind = 'inv');

	insert into pg_temp.export_data("id", "kind", "data")
        select null, 'VehicleModuleInventory',
			_character_transfer_top_level_export('VehicleModuleInventory', to_jsonb(vehicle_module_inventories))
            from vehicle_module_inventories where inventory_id IN (select id from pg_temp.export_data where kind = 'inv');

	-- Base backups (related actors are already added in the actors section)
	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'Building', _character_transfer_top_level_export('Building', to_jsonb(buildings))
			from buildings where id in (select id from pg_temp.export_data where kind = 'act');

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'BuildingInstance', _character_transfer_top_level_export('BuildingInstance', to_jsonb(building_instances))
			from building_instances where building_id in (select id from pg_temp.export_data where kind = 'act');

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'Placeable', _character_transfer_top_level_export('Placeable', to_jsonb(placeables))
			from placeables where id in (select id from pg_temp.export_data where kind = 'act');

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'Totem', _character_transfer_top_level_export('Totem', to_jsonb(totems))
			from totems where id in (select id from pg_temp.export_data where kind = 'act');

	insert into pg_temp.export_data("id", "kind", "data")
		select id, 'BaseBackup', _character_transfer_top_level_export('BaseBackup', to_jsonb(base_backups))
			from base_backups where player_id=v_player_controller_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select id, 'BaseBackupLinkedActor', _character_transfer_top_level_export('BaseBackupLinkedActor', to_jsonb(base_backup_linked_actors))
			from base_backup_linked_actors where id IN (
				select id from pg_temp.export_data where kind='BaseBackup'
			);

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'LandclaimSegment', _character_transfer_top_level_export('LandclaimSegment', to_jsonb(landclaim_segments))
			from landclaim_segments where totem_id IN (
				select id from pg_temp.export_data where kind='act'
			);

	insert into pg_temp.export_data("id", "kind", "data")
		select id, 'TaxInvoice', _character_transfer_top_level_export('TaxInvoice', to_jsonb(tax_invoice))
			from tax_invoice where totem_id IN (
				select id from pg_temp.export_data where kind='act'
			);

	-- Other stuff

	insert into pg_temp.export_data("id", "kind", "data")
		select id, 'Faction', _character_transfer_top_level_export('Faction', to_jsonb(factions))
			from factions;

	insert into pg_temp.export_data("id", "kind", "data")
		select id, 'Tutorial', _character_transfer_top_level_export('Tutorial', to_jsonb(tutorials))
			from tutorials where id IN (select distinct tutorial_id from tutorial_per_player where player_id=v_player_controller_id);

	insert into pg_temp.export_data("id", "kind", "data")
		select id, 'Keystone', _character_transfer_top_level_export('Keystone', to_jsonb(specialization_keystones_map))
			from specialization_keystones_map where id IN (select distinct keystone_id from purchased_specialization_keystones where player_id=v_player_controller_id);

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'RespawnLocation',
				_character_transfer_top_level_export('RespawnLocation', to_jsonb(player_respawn_locations))
				|| jsonb_build_object('id', gen_random_uuid())
				-- TODO: add default to id and just delete it by filter
			from player_respawn_locations
			where account_id=v_acc_id and "group" = ANY('{PlayerStart,Checkpoint,CheckpointSafe}');

	insert into pg_temp.export_data("kind", "data")
        select 'Character', _character_transfer_top_level_export('Character', to_jsonb(player_state) - 'last_avatar_activity' - 'reconnect_grace_period_end' - 'previous_server_partition_id')
			from player_state where player_state.account_id=v_acc_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'PlayerMarker', _character_transfer_top_level_export('PlayerMarker', to_jsonb(player_markers))
			from player_markers join markers using (marker_hash_id, dimension_index, map_name_id) join map_names using (map_name_id)
				where dimension_index=-1 and player_id=v_player_controller_id and map_name <> 'DeepDesert'
				and ((marker).payload_type <> 'EMarkerPayloadType::Default' or ((marker).payload_type = 'EMarkerPayloadType::Default' and (marker).marker_type like 'FlourSand%'));

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'Marker', _character_transfer_top_level_export('Marker', to_jsonb(markers))
			from markers join player_markers using (marker_hash_id, dimension_index, map_name_id) join map_names using (map_name_id)
				where dimension_index=-1 and player_id=v_player_controller_id and map_name <> 'DeepDesert'
				and ((marker).payload_type <> 'EMarkerPayloadType::Default' or ((marker).payload_type = 'EMarkerPayloadType::Default' and (marker).marker_type like 'FlourSand%'));

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'DialogueMetNpc', _character_transfer_top_level_export('DialogueMetNpc', to_jsonb(dialogue_met_npcs))
			from dialogue_met_npcs where player_id=v_player_controller_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'DialogueTakenNode', _character_transfer_top_level_export('DialogueTakenNode', to_jsonb(dialogue_taken_nodes))
			from dialogue_taken_nodes where player_id=v_player_controller_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'PlayerFaction', _character_transfer_top_level_export('PlayerFaction', to_jsonb(player_faction))
			from player_faction where actor_id=v_player_controller_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'PlayerFactionReputation', _character_transfer_top_level_export('PlayerFactionReputation', to_jsonb(player_faction_reputation))
			from player_faction_reputation where actor_id=v_player_controller_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'ConsumedLore', _character_transfer_top_level_export('ConsumedLore', to_jsonb(consumed_per_player_lore))
			from consumed_per_player_lore where actor_id=v_player_controller_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'PlayerTutorial', _character_transfer_top_level_export('PlayerTutorial', to_jsonb(tutorial_per_player))
			from tutorial_per_player where player_id=v_player_controller_id;

    insert into pg_temp.export_data("id", "kind", "data")
		select null, 'PurchasedKeystone', _character_transfer_top_level_export('PurchasedKeystone', to_jsonb(purchased_specialization_keystones))
			from purchased_specialization_keystones where player_id=v_player_controller_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'SpecializationTracks', _character_transfer_top_level_export('SpecializationTracks', to_jsonb(specialization_tracks))
			from specialization_tracks where player_id=v_player_controller_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'SpecializationRefund', _character_transfer_top_level_export('SpecializationRefund', to_jsonb(specialization_refund_id))
			from specialization_refund_id where player_id=v_player_controller_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'BuildingFavorite', _character_transfer_top_level_export('BuildingFavorite', to_jsonb(building_favorites))
			from building_favorites where account_id=v_acc_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'BuildingProgression', _character_transfer_top_level_export('BuildingProgression', to_jsonb(building_progression))
			from building_progression where account_id=v_acc_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'CommuninetPlayer', _character_transfer_top_level_export('CommuninetPlayer', to_jsonb(communinet_player))
			from communinet_player where account_id=v_acc_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'CommuninetPlayerChannel', _character_transfer_top_level_export('CommuninetPlayerChannel', to_jsonb(communinet_player_channels))
			from communinet_player_channels where account_id=v_acc_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'JourneyStoryNode', _character_transfer_top_level_export('JourneyStoryNode', to_jsonb(journey_story_node))
			from journey_story_node where account_id=v_acc_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'MapArea', _character_transfer_top_level_export('MapArea', to_jsonb(map_areas))
			from map_areas where account_id=v_acc_id and map_name <> 'DeepDesert';

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'PlayerAccessCode', _character_transfer_top_level_export('PlayerAccessCode', to_jsonb(player_access_codes))
			from player_access_codes where account_id=v_acc_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'PlayerTag', _character_transfer_top_level_export('PlayerTag', to_jsonb(player_tags))
			from player_tags where account_id=v_acc_id;

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'Sinkchart', _character_transfer_top_level_export('Sinkchart', to_jsonb(sinkcharts))
			from sinkcharts where item_id IN (select id from pg_temp.export_data where kind='itm');

	insert into pg_temp.export_data("id", "kind", "data")
		select id, 'bbp', _character_transfer_top_level_export('bbp', to_jsonb(building_blueprints))
			from building_blueprints where item_id IN (select id from pg_temp.export_data where kind='itm');

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'BuildingBlueprintInstance', _character_transfer_top_level_export('BuildingBlueprintInstance', to_jsonb(building_blueprint_instances))
			from building_blueprint_instances where building_blueprint_id IN (select id from pg_temp.export_data where kind='bbp');

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'BuildingBlueprintPlaceable', _character_transfer_top_level_export('BuildingBlueprintPlaceable', to_jsonb(building_blueprint_placeables))
			from building_blueprint_placeables where building_blueprint_id IN (select id from pg_temp.export_data where kind='bbp');

	insert into pg_temp.export_data("id", "kind", "data")
		select null, 'BuildingBlueprintPentashield', _character_transfer_top_level_export('BuildingBlueprintPentashield', to_jsonb(building_blueprint_pentashields))
			from building_blueprint_pentashields where building_blueprint_id IN (select id from pg_temp.export_data where kind='bbp');

	insert into pg_temp.export_data("id", "kind", "data")
	    select player_controller_id, 'PlayerVirtualCurrencyBalance', _character_transfer_top_level_export('PlayerVirtualCurrencyBalance', to_jsonb(player_virtual_currency_balances))
            from player_virtual_currency_balances where player_controller_id = v_player_controller_id;

    insert into pg_temp.export_data("id", "kind", "data")
	    select completion_id, 'DungeonCompletion', _character_transfer_top_level_export('DungeonCompletion', to_jsonb(dungeon_completion))
            from dungeon_completion where completion_id IN (select completion_id from dungeon_completion_players where player_id = v_player_controller_id);

	insert into pg_temp.export_data("id", "kind", "data")
	    select completion_id, 'DungeonCompletionPlayer', _character_transfer_top_level_export('DungeonCompletionPlayer', to_jsonb(dungeon_completion_players))
            from dungeon_completion_players where player_id = v_player_controller_id;

    insert into pg_temp.export_data("id","kind","data")
        select null, 'LandsraadHouseRewards', _character_transfer_top_level_export('LandsraadHouseRewards', to_jsonb(landsraad_house_rewards))
            from landsraad_house_rewards where player_id = v_player_controller_id;

    update pg_temp.export_data set data=_character_transfer_replace_local_id_with_transfer_id_in_json(data, '');

    return (select jsonb_build_object(
        '_patches_checksum', (_character_transfer_get_patches_checksum()),
        'funcom_id', (v_funcom_id),
        'player_controller_id', (v_player_controller_id),
        'entries', (_character_transfer_data_table_save())
    ));
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58162
f
character_transfer_get_unsaved_counts
in_fls_id text
TABLE(unsaved_bases_count bigint, unsaved_vehicles_count bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.character_transfer_get_unsaved_counts(in_fls_id text)
 RETURNS TABLE(unsaved_bases_count bigint, unsaved_vehicles_count bigint)
 LANGUAGE plpgsql
AS $function$
DECLARE
	v_acc_id BIGINT;
BEGIN
	v_acc_id := (SELECT acc.id FROM encrypted_accounts acc WHERE acc.user = in_fls_id);

	RETURN QUERY
		WITH unbacked_bases AS (
			SELECT COUNT(*) AS count
			FROM get_unsaved_base_totem_ids_for_account(v_acc_id)
		),
		unsaved_vehicles AS (
			SELECT COUNT(*) AS count
			FROM get_unbacked_up_vehicle_ids_for_account(v_acc_id)
		)
		SELECT
			ub.count AS unsaved_bases_count,
			uv.count AS unsaved_vehicles_count
		FROM unbacked_bases ub, unsaved_vehicles uv;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58163
f
character_transfer_import
in_data jsonb, in_fls_id text, in_character_name text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.character_transfer_import(in_data jsonb, in_fls_id text, in_character_name text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_checksum TEXT;
	v_coriolis_seed BigInt;
	v_transfer_coriolis_seed BigInt;
	v_id BigInt;
	v_kind _CharacterTransferEntryKind;
	v_new_player_controller_id BigInt;
	v_vehicle_ids BigInt[];
BEGIN
	if not (select is_player_offline(in_fls_id)) then
        raise exception 'sbRP2$ - Player must be Offline';
    end if;

    PERFORM delete_account(in_fls_id, 'incoming char transfer');

    v_checksum := (select _character_transfer_get_patches_checksum());
    IF NOT in_data->>'_patches_checksum' = v_checksum THEN
        raise exception 'sb9R2$ - Patches checksum mismatch, expected: %, got: %', v_checksum, in_data->>'_patches_checksum';
    END IF;

	perform _character_transfer_create_data_table();
	perform _character_transfer_data_table_load(in_data->'entries');

	update pg_temp.export_data set data=_character_transfer_replace_transfer_id_with_local_id_in_json(data, '');

	-- accounts

	insert into encrypted_accounts
		select (jsonb_populate_record(
			null::encrypted_accounts,
			_character_transfer_top_level_import(kind, data, id)
			|| jsonb_build_object(
				'encrypted_funcom_id', encrypt_user_data(in_data->>'funcom_id'),
				'user', in_fls_id
			)
		)).*
		from pg_temp.export_data
		where kind = 'acc';

	-- player and vehicle actors

	insert into actors
		select (jsonb_populate_record(null::actors, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'act';

	insert into fgl_entities
		select (jsonb_populate_record(null::fgl_entities, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'fgl';

	insert into actor_fgl_entities
		select (jsonb_populate_record(null::actor_fgl_entities, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'fgl';

	insert into actor_state
		select (jsonb_populate_record(null::actor_state, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'ActorState';

	insert into permission_actor
		select (jsonb_populate_record(null::permission_actor, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'PermissionActor';

	insert into permission_actor_rank
		select (jsonb_populate_record(null::permission_actor_rank, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'PermissionActorRank';


	-- vehicle and vehicle modules

	insert into vehicles
		select (jsonb_populate_record(null::vehicles, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'Vehicle';

	insert into vehicle_modules
		select (jsonb_populate_record(null::vehicle_modules, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'VehicleModule';

	-- You can VBT as co-owner, but then there will be no owner in target BG. This is a state that permission system doesn't support.
	-- So for backed up vehicles we also update the players ownership so they are now set as owner.
	WITH inserted_backup_vehicles AS (
			INSERT INTO backup_vehicles
				SELECT (jsonb_populate_record(null::backup_vehicles, _character_transfer_top_level_import(kind, data, id))).*
				FROM pg_temp.export_data
				WHERE kind = 'BackupVehicle'
			RETURNING vehicle_id
		)
		SELECT array_agg(vehicle_id) INTO v_vehicle_ids FROM inserted_backup_vehicles;
	perform _character_transfer_ensure_player_is_owner_of_vbt_vehicle(v_vehicle_ids);

	insert into recovered_vehicles
		select (jsonb_populate_record(null::recovered_vehicles, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'RecoveredVehicle';

	-- inventories and items

	insert into inventories
		select (jsonb_populate_record(null::inventories, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data e
		where kind = 'inv';

	insert into items
		select (jsonb_populate_record(null::items, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'itm';

	insert into actor_inventories
		select (jsonb_populate_record(null::actor_inventories, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'ActorInventory';

	insert into vehicle_module_inventories
		select (jsonb_populate_record(null::vehicle_module_inventories, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'VehicleModuleInventory';

	-- Base backups
	insert into buildings
		select (jsonb_populate_record(null::buildings, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'Building';

	insert into building_instances
		select (jsonb_populate_record(null::building_instances, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'BuildingInstance';

	insert into placeables
		select (jsonb_populate_record(null::placeables, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'Placeable';

	insert into totems
		select (jsonb_populate_record(null::totems, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'Totem';

	insert into base_backups
		select (jsonb_populate_record(null::base_backups, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'BaseBackup';

	insert into base_backup_linked_actors
		select (jsonb_populate_record(null::base_backup_linked_actors, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'BaseBackupLinkedActor';

	insert into landclaim_segments
		select (jsonb_populate_record(null::landclaim_segments, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'LandclaimSegment';

	insert into tax_invoice
		select (jsonb_populate_record(null::tax_invoice, _character_transfer_top_level_import(kind, data, id))).*
		from pg_temp.export_data
		where kind = 'TaxInvoice';

	-- other stuff

	insert into encrypted_player_state
		select (jsonb_populate_record(
			null::encrypted_player_state,
			_character_transfer_top_level_import(kind, data, id)
			|| jsonb_build_object(
				'encrypted_character_name', encrypt_user_data(in_character_name)
			)
		)).*
		from pg_temp.export_data
		where kind = 'Character'
		returning player_controller_id into v_new_player_controller_id;

	insert into player_respawn_locations
		select (
			jsonb_populate_record(null::player_respawn_locations, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'RespawnLocation';

	insert into markers
		select (
			jsonb_populate_record(null::markers, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'Marker'
	on conflict ("marker_hash_id", "dimension_index", "map_name_id") do nothing;

	insert into player_markers
		select (
			jsonb_populate_record(null::player_markers, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'PlayerMarker';

	insert into dialogue_met_npcs
		select (
			jsonb_populate_record(null::dialogue_met_npcs, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'DialogueMetNpc';

	insert into dialogue_taken_nodes
		select (
			jsonb_populate_record(null::dialogue_taken_nodes, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'DialogueTakenNode';

	insert into player_faction
		select (
			jsonb_populate_record(null::player_faction, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'PlayerFaction';

	insert into player_faction_reputation
		select (
			jsonb_populate_record(null::player_faction_reputation, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'PlayerFactionReputation';

	insert into consumed_per_player_lore
		select (
			jsonb_populate_record(null::consumed_per_player_lore, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'ConsumedLore';

	insert into tutorial_per_player
		select (
			jsonb_populate_record(null::tutorial_per_player, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'PlayerTutorial';

    insert into purchased_specialization_keystones
		select (
			jsonb_populate_record(null::purchased_specialization_keystones, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'PurchasedKeystone';

    insert into specialization_tracks
		select (
			jsonb_populate_record(null::specialization_tracks, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'SpecializationTracks';

    insert into specialization_refund_id
		select (
			jsonb_populate_record(null::specialization_refund_id, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'SpecializationRefund';

	insert into building_favorites
		select (
			jsonb_populate_record(null::building_favorites, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'BuildingFavorite';

	insert into building_progression
		select (
			jsonb_populate_record(null::building_progression, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'BuildingProgression';

	insert into communinet_player
		select (
			jsonb_populate_record(null::communinet_player, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'CommuninetPlayer';

	insert into communinet_player_channels
		select (
			jsonb_populate_record(null::communinet_player_channels, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'CommuninetPlayerChannel';

	insert into journey_story_node
		select (
			jsonb_populate_record(null::journey_story_node, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'JourneyStoryNode';

	insert into map_areas
		select (
			jsonb_populate_record(null::map_areas, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'MapArea';

	insert into player_access_codes
		select (
			jsonb_populate_record(null::player_access_codes, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'PlayerAccessCode';

	insert into player_tags
		select (
			jsonb_populate_record(null::player_tags, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'PlayerTag';

	insert into sinkcharts
		select (
			jsonb_populate_record(null::sinkcharts, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'Sinkchart';

	insert into building_blueprints
		select (
			jsonb_populate_record(null::building_blueprints, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'bbp';

	insert into building_blueprint_instances
		select (
			jsonb_populate_record(null::building_blueprint_instances, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'BuildingBlueprintInstance';

	insert into building_blueprint_placeables
		select (
			jsonb_populate_record(null::building_blueprint_placeables, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'BuildingBlueprintPlaceable';

	insert into building_blueprint_pentashields
		select (
			jsonb_populate_record(null::building_blueprint_pentashields, _character_transfer_top_level_import(kind, data, id))
		).*
		from pg_temp.export_data
		where kind = 'BuildingBlueprintPentashield';

	insert into player_virtual_currency_balances
        select (
            jsonb_populate_record(null::player_virtual_currency_balances, _character_transfer_top_level_import(kind, data, id))
        ).*
        from pg_temp.export_data
        where kind = 'PlayerVirtualCurrencyBalance';

	insert into dungeon_completion
        select (
            jsonb_populate_record(null::dungeon_completion, _character_transfer_top_level_import(kind, data, id))
        ).*
        from pg_temp.export_data
        where kind = 'DungeonCompletion';

	insert into dungeon_completion_players
        select (
            jsonb_populate_record(null::dungeon_completion_players, _character_transfer_top_level_import(kind, data, id))
        ).*
        from pg_temp.export_data
        where kind = 'DungeonCompletionPlayer';

    insert into landsraad_house_rewards
        select (
                   jsonb_populate_record(null::landsraad_house_rewards, _character_transfer_top_level_import(kind, data, id))
                   ).*
        from pg_temp.export_data
        where kind = 'LandsraadHouseRewards';

	PERFORM set_character_import_state(in_fls_id, 'Complete'::TransferImportState);
	return v_new_player_controller_id;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58165
f
clean_expired_party_invites
in_invite_expire_seconds integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.clean_expired_party_invites(in_invite_expire_seconds integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	PERFORM parties_get_exclusive_operation_lock();

	PERFORM remove_party_invite(invite_id, 0::smallint) FROM party_invites
	WHERE CURRENT_TIMESTAMP > TO_TIMESTAMP(invite_sent_timespan) + INTERVAL '1 second' * in_invite_expire_seconds;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58166
f
clean_guild_invites_with_incompatible_faction
in_player_id bigint, in_faction_id smallint, neutral_faction_id smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.clean_guild_invites_with_incompatible_faction(in_player_id bigint, in_faction_id smallint, neutral_faction_id smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	row_record record; -- Variable to hold individual rows
	out_guild_faction_id SMALLINT;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	FOR row_record IN
		SELECT invite_id, guild_invites.guild_id, guilds.guild_faction FROM get_player_guild_invites(in_player_id) as guild_invites
		JOIN guilds ON guilds.guild_id = guild_invites.guild_id
	LOOP	
		IF row_record.guild_faction != neutral_faction_id AND in_faction_id != neutral_faction_id AND row_record.guild_faction != in_faction_id THEN
			PERFORM reject_guild_invite(row_record.invite_id);
		END IF;
    END LOOP;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58167
f
clean_old_guild_invites
in_cutoff_timespan bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.clean_old_guild_invites(in_cutoff_timespan bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM guild_invites WHERE invite_sent_timespan < in_cutoff_timespan;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58168
f
clean_stock_for_player
in_player_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.clean_stock_for_player(in_player_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM vendor_stock_cycle WHERE player_id = in_player_id;
	DELETE FROM vendor_stock_state WHERE player_id = in_player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58169
f
clean_stock_for_vendors
in_vendor_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.clean_stock_for_vendors(in_vendor_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM vendor_stock_cycle WHERE vendor_id = ANY(in_vendor_ids);
	DELETE FROM vendor_stock_state WHERE vendor_id = ANY(in_vendor_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58170
f
clean_vendors_older_than_timestamp
in_reference_timestamp bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.clean_vendors_older_than_timestamp(in_reference_timestamp bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
   vendors_to_delete TEXT[];
BEGIN
	SELECT array_agg(vendor_id) INTO vendors_to_delete FROM vendor_stock_cycle WHERE last_interacted_timestamp <= in_reference_timestamp;
	DELETE FROM vendor_stock_cycle WHERE vendor_id = ANY(vendors_to_delete);
	DELETE FROM vendor_stock_state WHERE vendor_id = ANY(vendors_to_delete);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58171
f
cleanup_account_log_and_orphaned_actors

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.cleanup_account_log_and_orphaned_actors()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    delete from actors
        WHERE
            -- not is null instead of is not null to match the index expression
            not owner_account_id is null
            AND NOT EXISTS(select 1 from accounts where id=owner_account_id);
    truncate account_removal_log;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58172
f
cleanup_accounts_marked_for_deletion_in_fls
in_account_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.cleanup_accounts_marked_for_deletion_in_fls(in_account_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	perform delete_account(id, 'deleted in fls') from unnest(in_account_ids) as id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58173
f
cleanup_orphaned_entities

trigger

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.cleanup_orphaned_entities()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM fgl_entities WHERE fgl_entities.entity_id = OLD.entity_id;
	RETURN NULL;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58174
f
clear_map_areas_data_for_player
in_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.clear_map_areas_data_for_player(in_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM map_areas WHERE account_id = in_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58175
f
complete_journey_nodes_where_prerequisite_nodes_are_complete
story_ids_to_complete text[], prerequisite_completed_story_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.complete_journey_nodes_where_prerequisite_nodes_are_complete(story_ids_to_complete text[], prerequisite_completed_story_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF ARRAY_LENGTH(story_ids_to_complete, 1) = 0 OR ARRAY_LENGTH(prerequisite_completed_story_ids, 1) = 0 THEN
		RAISE EXCEPTION 'The story ids to complete array and/or the prerequisite completed story ids array is empty - neither array may be empty';
	END IF;

	WITH account_ids_to_modify AS (
		SELECT account_id
		FROM journey_story_node
		WHERE story_node_id = ANY(prerequisite_completed_story_ids)
		AND complete_condition_state = to_jsonb(true)
		GROUP BY account_id
		HAVING COUNT(DISTINCT story_node_id) = ARRAY_LENGTH(prerequisite_completed_story_ids, 1)
	)
	INSERT INTO journey_story_node(account_id, story_node_id, override_reward_block, has_pending_reward, complete_condition_state, reveal_condition_state, metadata_state, reset_group, fail_condition_state)
	SELECT ids.account_id, completed_node.story_node_id, false, false, to_jsonb(true), to_jsonb(true), '{}', 'Default', '{}'
	FROM account_ids_to_modify AS ids
	CROSS JOIN (
		SELECT story_node_id
		FROM UNNEST(story_ids_to_complete) AS story_node_id
	) completed_node(story_node_id)
	ON CONFLICT ON CONSTRAINT journey_story_node_pkey
		DO UPDATE SET
			override_reward_block = EXCLUDED.override_reward_block,
            has_pending_reward = EXCLUDED.has_pending_reward,
			complete_condition_state = EXCLUDED.complete_condition_state,
			reveal_condition_state = EXCLUDED.reveal_condition_state;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58176
f
complete_journey_story_nodes_for_player
in_player_id text, in_story_node_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.complete_journey_story_nodes_for_player(in_player_id text, in_story_node_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF NOT is_player_offline(in_player_id) THEN
		RAISE EXCEPTION 'Cannot execute query because the player is online - they must be offline in order for the journey data to be updated correctly without risking it being overwritten by player actions.';
	END IF;

	WITH player_account_id AS (
		SELECT id
		FROM accounts a
		WHERE a.user = in_player_id
	)
	INSERT INTO journey_story_node(account_id, story_node_id, override_reward_block, has_pending_reward, complete_condition_state, reveal_condition_state, fail_condition_state, metadata_state, reset_group)
	SELECT player_account_id.id, completed_node.story_node_id, completed_node.override_reward_block, completed_node.has_pending_reward, completed_node.complete_condition_state, completed_node.reveal_condition_state, completed_node.fail_condition_state, completed_node.metadata_state, completed_node.reset_group
	FROM player_account_id 
	CROSS JOIN (
		SELECT story_node_id, false, false, to_jsonb(true), to_jsonb(true), jsonb_object(ARRAY[]::text[]), jsonb_object(ARRAY[]::text[]), 'Default'::JourneyStoryResetGroup
		FROM UNNEST(in_story_node_ids) AS story_node_id
	) completed_node(story_node_id, override_reward_block, has_pending_reward, complete_condition_state, reveal_condition_state, fail_condition_state, metadata_state, reset_group)
	ON CONFLICT ON CONSTRAINT journey_story_node_pkey
		DO UPDATE SET
			complete_condition_state = EXCLUDED.complete_condition_state,
			reveal_condition_state = EXCLUDED.reveal_condition_state,
			fail_condition_state = EXCLUDED.fail_condition_state,
			metadata_state = EXCLUDED.metadata_state;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58177
f
corilis_cleanup_map
in_server_info dune.serverinfo, in_map_info dune.coriolismapinfo
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.corilis_cleanup_map(in_server_info dune.serverinfo, in_map_info dune.coriolismapinfo)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	PERFORM delete_markers_for_all_players(in_map_info.marker_types_to_keep, in_server_info.map);

	IF in_map_info.should_clear_surveyed_areas
	THEN
		DELETE FROM map_areas WHERE map_name = in_server_info.map;
	END IF;

	IF in_map_info.is_outside_shieldwall
	THEN
		DELETE FROM resourcefield_state WHERE map = in_server_info.map;
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58178
f
coriolis_cleanup_farm
in_server_info dune.serverinfo, in_map_info dune.coriolismapinfo
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.coriolis_cleanup_farm(in_server_info dune.serverinfo, in_map_info dune.coriolismapinfo)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM lore_pickups_temporary;
	DELETE FROM consumed_temporary_per_player_lore;
	UPDATE player_state SET is_coriolis_processed = FALSE;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58179
f
coriolis_cleanup_partition
in_server_info dune.serverinfo, in_map_info dune.coriolismapinfo
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.coriolis_cleanup_partition(in_server_info dune.serverinfo, in_map_info dune.coriolismapinfo)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF in_map_info.is_outside_shieldwall
	THEN
        PERFORM delete_actors_and_respawns_on_server(in_server_info, (in_map_info).vehicle_classes_spawned_on_map, TRUE);

		update player_state set life_state='DeadByCoriolis'
			from actors
			where player_state.player_pawn_id=actors.id AND server_info_match(actors, in_server_info) and
			not exists (SELECT 1 FROM actor_state WHERE actor_state.actor_id = player_state.player_pawn_id AND actor_state.state = 'Travel');

		-- Move players that died to Hagga Basin in their respective dimension and partition
		UPDATE actors
		SET
			map = 'HaggaBasin',
			dimension_index = player_state.return_dimension_index,
			partition_id = (
				SELECT world_partition.partition_id
				FROM world_partition
				WHERE player_state.return_dimension_index = world_partition.dimension_index AND world_partition.map = 'Survival_1'
			)
		FROM player_state
		WHERE
			(player_state.player_controller_id = actors.id OR player_state.player_pawn_id = actors.id OR player_state.player_state_id = actors.id) AND
			server_info_match(actors, in_server_info) AND
			NOT EXISTS (SELECT 1 FROM actor_state WHERE actor_state.actor_id = player_state.player_pawn_id AND actor_state.state = 'Travel');
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58180
f
coriolis_update_seed
in_server_info dune.serverinfo, in_new_coriolis_seed integer, in_map_info dune.coriolismapinfo
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.coriolis_update_seed(in_server_info dune.serverinfo, in_new_coriolis_seed integer, in_map_info dune.coriolismapinfo)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	old_farm_coriolis_seed Integer;
	old_map_coriolis_seed Integer;
	old_partition_coriolis_seed Integer;
BEGIN
	LOCK TABLE world_farm_reset_seed, world_map_reset_seed, world_partition_reset_seed IN EXCLUSIVE MODE;

	SELECT INTO old_farm_coriolis_seed world_reset_seed FROM world_farm_reset_seed WHERE onerow_id = TRUE limit 1;
	UPDATE world_farm_reset_seed SET world_reset_seed = in_new_coriolis_seed WHERE onerow_id = TRUE;

	SELECT INTO old_map_coriolis_seed world_reset_seed FROM world_map_reset_seed WHERE map = in_server_info.map limit 1;
	INSERT INTO world_map_reset_seed (map, world_reset_seed) Values(in_server_info.map, in_new_coriolis_seed)
		ON CONFLICT(map) DO
		UPDATE SET world_reset_seed = in_new_coriolis_seed;

	SELECT INTO old_partition_coriolis_seed world_reset_seed FROM world_partition_reset_seed WHERE partition_id = in_server_info.partition_id limit 1;
	IF in_server_info.partition_id IS NOT NULL
	THEN
		INSERT INTO world_partition_reset_seed (partition_id, world_reset_seed) Values(in_server_info.partition_id, in_new_coriolis_seed)
			ON CONFLICT(partition_id) DO
			UPDATE SET world_reset_seed = in_new_coriolis_seed;
	END IF;

	IF old_farm_coriolis_seed IS NULL OR old_farm_coriolis_seed <> in_new_coriolis_seed
	THEN
		PERFORM coriolis_cleanup_farm(in_server_info, in_map_info);
	END IF;

	IF in_map_info.is_affected_by_coriolis
	THEN
		IF old_map_coriolis_seed IS NULL OR old_map_coriolis_seed <> in_new_coriolis_seed
		THEN
			PERFORM corilis_cleanup_map(in_server_info, in_map_info);
		END IF;

		IF (in_server_info.partition_id IS NOT NULL AND (old_partition_coriolis_seed IS NULL OR old_partition_coriolis_seed <> in_new_coriolis_seed)) OR
		   (old_map_coriolis_seed IS NULL OR old_map_coriolis_seed <> in_new_coriolis_seed)
		THEN
			PERFORM coriolis_cleanup_partition(in_server_info, in_map_info);
		END IF;
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58181
f
create_event_log_partition

trigger

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.create_event_log_partition()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN
    CALL create_event_log_partition_table('event_log', NEW.partition_id);

    RETURN NEW;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58182
p
create_event_log_partition_table
IN table_name text, IN partition_id bigint


@@@BODY@@@
CREATE OR REPLACE PROCEDURE dune.create_event_log_partition_table(IN table_name text, IN partition_id bigint)
 LANGUAGE plpgsql
AS $procedure$
DECLARE 
    start_range BIGINT;
    end_range BIGINT;
    partition_table_name TEXT;
BEGIN
    start_range := partition_id;
    end_range := partition_id + 1;
    partition_table_name := format('%s_p%s', table_name, partition_id);

    EXECUTE format('
        CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
        FOR VALUES FROM (%s) TO (%s);
    ', partition_table_name, table_name, start_range, end_range);

END;
$procedure$

@@@ENDRTN@@@
@@@RTN@@@58183
f
create_guild
in_player_id bigint, in_neutral_faction smallint, in_guild_name text, in_guild_desc text, in_max_guild_count_per_player integer, OUT out_guild_id bigint, OUT out_success boolean, OUT out_fail_reason dune.guildcreatefailreason
record

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.create_guild(in_player_id bigint, in_neutral_faction smallint, in_guild_name text, in_guild_desc text, in_max_guild_count_per_player integer, OUT out_guild_id bigint, OUT out_success boolean, OUT out_fail_reason dune.guildcreatefailreason)
 RETURNS record
 LANGUAGE plpgsql
AS $function$
DECLARE
	guild_count integer;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	IF EXISTS (SELECT 1 FROM guilds WHERE guild_name ILIKE in_guild_name) THEN
		out_guild_id := 0;
		out_success := FALSE;
		out_fail_reason := 'NameAlreadyTaken'::GuildCreateFailReason; -- 1 represents NAME_ALREADY_EXISTS
		RETURN;
	END IF;

	-- we need to check if the player is already part of max amount of guilds before being able to add them
	SELECT INTO guild_count COUNT(*) FROM guild_members WHERE player_id = in_player_id;
	IF guild_count >= in_max_guild_count_per_player THEN
		out_guild_id := 0;
		out_success := FALSE;
		out_fail_reason := 'QueryError'::GuildCreateFailReason;
		RETURN;
	END IF;

	INSERT INTO guilds("guild_id", "guild_name", "guild_faction", "guild_description") VALUES(DEFAULT, in_guild_name, in_neutral_faction , in_guild_desc) RETURNING "guild_id" INTO out_guild_id;
	INSERT INTO guild_members("player_id", "guild_id", "role_id") VALUES(in_player_id, out_guild_id, 100);

	PERFORM pg_notify('guild_notify_channel', format('add_player#{"PlayerId" : %s , "PlayerFactionId" : %s, "GuildId" : %s, "RoleId" : 100, "ShouldClearInvites" : 0}', in_player_id, get_player_faction(in_player_id, in_neutral_faction), out_guild_id));

	out_success := TRUE;
	out_fail_reason := 'None'::GuildCreateFailReason;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58184
f
create_or_update_tutorial_entry
in_player_id bigint, in_tutorial_id smallint, in_tutorial_state smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.create_or_update_tutorial_entry(in_player_id bigint, in_tutorial_id smallint, in_tutorial_state smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO tutorial_per_player("player_id", "tutorial_id", "tutorial_state") VALUES(in_player_id, in_tutorial_id, in_tutorial_state)
    ON CONFLICT (player_id, tutorial_id) DO UPDATE SET "player_id" = in_player_id, "tutorial_id" = in_tutorial_id, "tutorial_state" = in_tutorial_state
    WHERE tutorial_per_player.player_id = in_player_id AND tutorial_per_player.tutorial_id = in_tutorial_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58185
f
create_server_player_access_codes
in_account_id bigint, in_access_code integer, in_access_code_type integer, in_is_resettable boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.create_server_player_access_codes(in_account_id bigint, in_access_code integer, in_access_code_type integer, in_is_resettable boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO player_access_codes(account_id, access_code, access_code_type,is_resettable)
    VALUES(in_account_id, in_access_code, in_access_code_type, in_is_resettable);
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58186
f
create_sinkchart_for_map_area_id
in_item_id bigint, in_creator_id bigint, in_map_name text, in_area_id smallint
integer

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.create_sinkchart_for_map_area_id(in_item_id bigint, in_creator_id bigint, in_map_name text, in_area_id smallint)
 RETURNS integer
 LANGUAGE plpgsql
AS $function$
DECLARE
	sinkchart_marker_ids BIGINT[];
	sinkchart_array_length INT;
BEGIN
	-- Aggregate marker hash IDs into an array based on the conditions
	SELECT ARRAY_AGG(markers.marker_hash_id) INTO sinkchart_marker_ids
	FROM player_markers,
         markers JOIN map_names USING(map_name_id)
	WHERE player_markers.player_id = in_creator_id
		AND map_names.map_name = in_map_name
		AND player_markers.map_name_id = markers.map_name_id
		AND player_markers.marker_hash_id = markers.marker_hash_id
		AND markers.area_id = in_area_id
		AND (player_markers.discovery_level = 2 OR player_markers.discovery_level = 3); -- 'EMarkerDiscoveryLevel::Mysterious' OR 'EMarkerDiscoveryLevel::Discovered'

	-- Check if is NOT NULL before proceeding with the INSERT
	IF array_length(sinkchart_marker_ids, 1) IS NOT NULL THEN
		INSERT INTO sinkcharts (item_id, marker_hash_ids)
		VALUES (in_item_id, sinkchart_marker_ids)
		RETURNING array_length(marker_hash_ids, 1) INTO sinkchart_array_length;

		RETURN sinkchart_array_length;
	ELSE
		RETURN 0;
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58187
f
debug_add_test_table_data
in_entry text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.debug_add_test_table_data(in_entry text)
 RETURNS void
 LANGUAGE sql
AS $function$
	insert into debug_test_table("entry") VALUES (in_entry);
$function$

@@@ENDRTN@@@
@@@RTN@@@58188
f
debug_collect_test_table_data

SETOF text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.debug_collect_test_table_data()
 RETURNS SETOF text
 LANGUAGE sql
AS $function$
	select entry from debug_test_table;
$function$

@@@ENDRTN@@@
@@@RTN@@@58189
f
debug_echo
in_text text, in_notices text[]
text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.debug_echo(in_text text, in_notices text[])
 RETURNS text
 LANGUAGE plpgsql
AS $function$
BEGIN
	perform debug_raise_notices(in_notices);
	return in_text;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58190
f
debug_get_coriolis_seeds

TABLE(farm_seed integer, map_names text[], map_seeds integer[], partitions_ids bigint[], partitions_map text[], partitions_seeds integer[])

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.debug_get_coriolis_seeds()
 RETURNS TABLE(farm_seed integer, map_names text[], map_seeds integer[], partitions_ids bigint[], partitions_map text[], partitions_seeds integer[])
 LANGUAGE plpgsql
AS $function$
begin
	RETURN QUERY
		WITH
			map_seeds as (
				SELECT array_agg(map_name) as map_name, array_agg(seed) as seed
				FROM (
						SELECT COALESCE(map_seed.map, partition.map) AS map_name, COALESCE(map_seed.world_reset_seed, -1) AS seed
						FROM world_map_reset_seed AS map_seed FULL JOIN world_partition as partition ON map_seed.map = partition.map
						GROUP BY map_seed.map, partition.map, map_seed.world_reset_seed
						ORDER BY map_name ASC
					) as maps_temp

			),
			partitions_seeds as (
				SELECT array_agg(partition_id) as partition_id, array_agg(map_name) as map_name, array_agg(seed) as seed
				FROM (
						SELECT partition.partition_id as partition_id, partition.map as map_name, COALESCE(partition_seed.world_reset_seed, -1) AS seed
						FROM world_partition_reset_seed AS partition_seed FULL JOIN world_partition as partition ON partition_seed.partition_id = partition.partition_id
						GROUP BY partition.map, partition.partition_id, partition_seed.world_reset_seed
						ORDER BY partition.partition_id ASC
					) as partitions_temp
			)
		SELECT
			COALESCE(world_reset_seed, -1),
			COALESCE(map_seeds.map_name, array[]::TEXT[]),
			COALESCE(map_seeds.seed, array[]::Integer[]),
			COALESCE(partitions_seeds.partition_id, array[]::BigInt[]),
			COALESCE(partitions_seeds.map_name, array[]::TEXT[]),
			COALESCE(partitions_seeds.seed, array[]::Integer[])
		FROM
			world_farm_reset_seed,
			map_seeds,
			partitions_seeds;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58191
f
debug_raise_exception
in_exception text, in_notices text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.debug_raise_exception(in_exception text, in_notices text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	perform debug_raise_notices(in_notices);
	RAISE EXCEPTION '%', in_exception;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58192
f
debug_raise_notices
in_notices text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.debug_raise_notices(in_notices text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF array_length(in_notices, 1) IS NOT NULL THEN
		FOR i IN 0..array_length(in_notices, 1)-1
		LOOP
			RAISE NOTICE '%', in_notices[i];
		END LOOP;
	END IF;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58193
f
debug_reset_test_table

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.debug_reset_test_table()
 RETURNS void
 LANGUAGE sql
AS $function$
	truncate debug_test_table;
$function$

@@@ENDRTN@@@
@@@RTN@@@58194
f
debug_set_farm_seed
in_new_coriolis_seed integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.debug_set_farm_seed(in_new_coriolis_seed integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	UPDATE world_farm_reset_seed SET world_reset_seed = in_new_coriolis_seed WHERE onerow_id = TRUE;

	UPDATE world_map_reset_seed SET world_reset_seed = in_new_coriolis_seed;
	INSERT INTO world_map_reset_seed SELECT map, in_new_coriolis_seed FROM world_partition GROUP BY map
		ON CONFLICT(map) DO
		UPDATE SET world_reset_seed = in_new_coriolis_seed;

	UPDATE world_partition_reset_seed SET world_reset_seed = in_new_coriolis_seed;
	INSERT INTO world_partition_reset_seed SELECT partition_id, in_new_coriolis_seed FROM world_partition GROUP BY partition_id
		ON CONFLICT(partition_id) DO
		UPDATE SET world_reset_seed = in_new_coriolis_seed;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58195
f
debug_set_map_seed
in_map text, in_new_coriolis_seed integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.debug_set_map_seed(in_map text, in_new_coriolis_seed integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	INSERT INTO world_map_reset_seed (map, world_reset_seed) Values(in_server_info.map, in_new_coriolis_seed)
		ON CONFLICT(map) DO
		UPDATE SET world_reset_seed = in_new_coriolis_seed;

	UPDATE world_partition_reset_seed SET world_reset_seed = in_new_coriolis_seed WHERE map = in_map;
	INSERT INTO world_partition_reset_seed SELECT partition_id, in_new_coriolis_seed FROM world_partition WHERE map = in_map GROUP BY partition_id
		ON CONFLICT(partition_id) DO
		UPDATE SET world_reset_seed = in_new_coriolis_seed;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58196
f
debug_set_partition_seed
in_partition_id bigint, in_new_coriolis_seed integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.debug_set_partition_seed(in_partition_id bigint, in_new_coriolis_seed integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	INSERT INTO world_partition_reset_seed (partition_id, world_reset_seed) Values(in_server_info.partition_id, in_new_coriolis_seed)
		ON CONFLICT(partition_id) DO
		UPDATE SET world_reset_seed = in_new_coriolis_seed;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58197
f
decrypt_user_data
in_encrypted_data bytea
text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.decrypt_user_data(in_encrypted_data bytea)
 RETURNS text
 LANGUAGE sql
 IMMUTABLE
AS $function$select convert_from(in_encrypted_data, 'utf8');$function$

@@@ENDRTN@@@
@@@RTN@@@58198
f
delete_account
in_user_id text, in_reason text
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_account(in_user_id text, in_reason text)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
DECLARE
    was_found Boolean;
BEGIN
    WITH referenced_actor_ids as (
        SELECT ARRAY[player_controller_id, player_pawn_id, player_state_id] as ids
            FROM player_state ps JOIN accounts a ON (ps.account_id = a.id)
            WHERE a.user = in_user_id
    ),
    deleted_actors AS
    (
        DELETE FROM actors USING referenced_actor_ids WHERE id IN (SELECT id FROM actors WHERE id = ANY(referenced_actor_ids.ids) ORDER BY id FOR UPDATE) returning id
    ),
    selected_respawn_beacons AS
    (
        SELECT player_respawn_locations.locator_actor_id FROM player_respawn_locations
        INNER JOIN accounts ON accounts.id = player_respawn_locations.account_id
        WHERE accounts.user = in_user_id AND player_respawn_locations.group = 'RespawnBeacon'
    ),
    delete_respawn_beacons AS
    (
        DELETE FROM actors
        WHERE actors.id IN (SELECT * FROM selected_respawn_beacons)
    ),
    delete_accounts as (
        DELETE FROM accounts WHERE accounts.user = in_user_id returning id as account_id
    ),
    insert_into_removal_log as (
        insert into account_removal_log("fls_id", "account_id", "reason") select in_user_id, account_id, in_reason from delete_accounts
    ),
    delete_from_cascades as (
        SELECT referenced_actor_ids.ids[1] as id,
            guild_handle_actor_delete(referenced_actor_ids.ids[1]),
            remove_party_member(referenced_actor_ids.ids[1], 0::SMALLINT),
            ownership_handle_actor_delete(referenced_actor_ids.ids[1]),
            perform_notify_on_character_delete(in_user_id)
            FROM referenced_actor_ids
    )
    SELECT count(*) > 0 INTO was_found FROM delete_from_cascades;
    return was_found;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58199
f
delete_actor_states_travel
in_actor_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_actor_states_travel(in_actor_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	WITH
		traveling_actor_ids AS (
			SELECT t.id FROM get_traveling_non_player_actor_ids(in_actor_id) AS t
		)
	DELETE FROM actor_state WHERE (actor_id IN (SELECT t.id FROM traveling_actor_ids AS t(id)) OR actor_id = in_actor_id) AND state = 'Travel';
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58200
f
delete_actors
in_ids bigint[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_actors(in_ids bigint[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM actors WHERE id = ANY(in_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58201
f
delete_actors_and_respawns_on_server
in_server_info dune.serverinfo, in_vehicle_classes_spawned_on_map text[], in_allow_vehicle_recovery boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_actors_and_respawns_on_server(in_server_info dune.serverinfo, in_vehicle_classes_spawned_on_map text[], in_allow_vehicle_recovery boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    WITH actors_to_delete AS (
	    SELECT a.id
        FROM actors a
        LEFT JOIN actor_state s ON a.id = s.actor_id
	    WHERE owner_account_id IS NULL
	    AND s.state IS DISTINCT FROM 'Travel'
	    AND s.state IS DISTINCT FROM 'VehicleBackup'
	    AND s.state IS DISTINCT FROM 'VehicleRecovery'
	    AND server_info_match(a, in_server_info)
        AND (
            -- Actors that are not vehicles should always be deleted
            NOT EXISTS (SELECT 1 FROM vehicles v WHERE v.id = a.id)
            -- Only vehicles that are allowed to be spawned on this map should be deleted
            OR in_vehicle_classes_spawned_on_map IS NULL -- If the list is NULL all vehicles are allowed
            OR a.class = ANY(in_vehicle_classes_spawned_on_map) -- Vehicle type is explicitly allowed on this map
        )
	    ORDER BY a.id FOR UPDATE OF a
    ),
    vehicles_to_recover AS (
        SELECT COALESCE(ARRAY_AGG(v.id), ARRAY[]::BIGINT[]) AS ids FROM actors_to_delete a JOIN vehicles v ON (a.id = v.id)
        WHERE in_allow_vehicle_recovery
    ),
    recovered_vehicles AS (
        SELECT ids, store_recovered_vehicles_wiped_before_spawn(ids) FROM vehicles_to_recover
    )
    DELETE FROM actors a USING recovered_vehicles rv
    WHERE a.id = ANY(SELECT id FROM actors_to_delete)
    AND NOT a.id = ANY(rv.ids);

	with
		deleted_ids as (
			DELETE from player_respawn_locations
				WHERE map = in_server_info.map AND dimension = in_server_info.dimension_index
				returning id
		)
		update player_state set pending_respawn_location_id=null
			where pending_respawn_location_id in (select * from deleted_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58202
f
delete_all_dungeon_completions
in_dungeon_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_all_dungeon_completions(in_dungeon_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	DELETE FROM dungeon_completion WHERE dungeon_id = in_dungeon_id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58203
f
delete_all_dungeon_completions_by_player
in_dungeon_id text, in_player_id bigint, in_keep_completion_for_other_players boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_all_dungeon_completions_by_player(in_dungeon_id text, in_player_id bigint, in_keep_completion_for_other_players boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	DELETE FROM dungeon_completion WHERE
		NOT in_keep_completion_for_other_players AND
		dungeon_id = in_dungeon_id AND 
		completion_id IN (SELECT completion_id FROM dungeon_completion_players WHERE player_id = in_player_id);

	DELETE FROM dungeon_completion_players
	WHERE
		in_keep_completion_for_other_players AND
		player_id = in_player_id AND
		completion_id IN (SELECT completion_id FROM dungeon_completion WHERE dungeon_id = in_dungeon_id);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58204
f
delete_all_dungeon_completions_for_all_dungeons_by_player
in_player_id bigint, in_keep_completion_for_other_players boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_all_dungeon_completions_for_all_dungeons_by_player(in_player_id bigint, in_keep_completion_for_other_players boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	DELETE FROM dungeon_completion WHERE
		NOT in_keep_completion_for_other_players AND
		completion_id IN (SELECT completion_id FROM dungeon_completion_players WHERE player_id = in_player_id);
		
	DELETE FROM dungeon_completion_players WHERE in_keep_completion_for_other_players AND player_id = in_player_id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58205
f
delete_all_inactive_farms

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_all_inactive_farms()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM farm_state WHERE server_id NOT IN (SELECT * FROM active_server_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58206
f
delete_all_journey_story_nodes
in_account_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_all_journey_story_nodes(in_account_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM journey_story_node WHERE account_id = in_account_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58207
f
delete_all_static_shifting_sand

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_all_static_shifting_sand()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	TRUNCATE shiftingsands_data;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58208
f
delete_all_tutorial_entries
in_player_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_all_tutorial_entries(in_player_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM tutorial_per_player WHERE tutorial_per_player.player_id = in_player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58209
f
delete_building_blueprint
in_building_item_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_building_blueprint(in_building_item_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM building_blueprints WHERE id = in_building_item_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58210
f
delete_character
in_actor_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_character(in_actor_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM actors WHERE id = in_actor_id;
    DELETE FROM properties WHERE object_id = in_actor_id;
    DELETE FROM fgl_data WHERE object_id = in_actor_id;
    DELETE FROM actor_transform WHERE actor_id = in_actor_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58211
f
delete_crafted_map
in_item_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_crafted_map(in_item_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM crafted_maps WHERE item_id = in_item_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58212
f
delete_dialogue_data
in_player_controller_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_dialogue_data(in_player_controller_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM dialogue_met_npcs
    WHERE player_id = in_player_controller_id;

    DELETE FROM dialogue_taken_nodes
    WHERE player_id = in_player_controller_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58213
f
delete_inventory_item
in_item_id bigint, in_count bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_inventory_item(in_item_id bigint, in_count bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	remaining_stack_size BIGINT;
BEGIN
	SELECT INTO STRICT remaining_stack_size stack_size FROM items WHERE id = in_item_id;

	remaining_stack_size := remaining_stack_size - in_count;

	IF remaining_stack_size < 0 THEN
		RETURN NULL;
	END IF;

	IF remaining_stack_size > 0 THEN
		UPDATE items SET stack_size = remaining_stack_size WHERE id = in_item_id;
	ELSE
		PERFORM delete_item(in_item_id);
	END IF;
	RETURN remaining_stack_size;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58214
f
delete_item
in_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_item(in_id bigint)
 RETURNS void
 LANGUAGE sql
AS $function$
	DELETE FROM items i
	USING inventories inv
	WHERE i.inventory_id = inv.id
	AND i.id = in_id
	RETURNING _add_item_delete_log(
		i.id,
		inv.id,
		i.template_id
	);
$function$

@@@ENDRTN@@@
@@@RTN@@@58215
f
delete_items
in_ids bigint[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_items(in_ids bigint[])
 RETURNS void
 LANGUAGE sql
AS $function$
	DELETE FROM items i
	USING inventories inv
	WHERE i.inventory_id = inv.id
	AND i.id = ANY (in_ids)
	RETURNING _add_item_delete_log(
		i.id,
		inv.id,
		i.template_id
	);
$function$

@@@ENDRTN@@@
@@@RTN@@@58216
f
delete_items_from_actor
in_actor_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_items_from_actor(in_actor_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	PERFORM delete_items(
		(
	        SELECT array_agg(i.id)
	        FROM items i
	        JOIN inventories inv ON inv.id = i.inventory_id
	        WHERE inv.actor_id = in_actor_id
		)
	);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58217
f
delete_journey_story_ids
story_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_journey_story_ids(story_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM journey_story_node
	WHERE story_node_id = ANY(story_ids);

	DELETE FROM journey_story_node_cooldown
	WHERE story_node_id = ANY(story_ids);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58218
f
delete_journey_story_node
in_account_id bigint, in_story_node_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_journey_story_node(in_account_id bigint, in_story_node_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM journey_story_node WHERE account_id = in_account_id AND story_node_id = in_story_node_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58219
f
delete_journey_story_nodes_for_group_for_player
in_account_id bigint, in_reset_group dune.journeystoryresetgroup
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_journey_story_nodes_for_group_for_player(in_account_id bigint, in_reset_group dune.journeystoryresetgroup)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM journey_story_node_cooldown
	WHERE story_node_id IN (
		SELECT story_node_id
		FROM journey_story_node a
		WHERE a.account_id = in_account_id AND a.reset_group = in_reset_group
	)
	AND account_id = in_account_id;

	DELETE FROM journey_story_node
	WHERE reset_group = in_reset_group AND account_id = in_account_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58220
f
delete_journey_story_nodes_for_player
in_player_id text, in_story_node_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_journey_story_nodes_for_player(in_player_id text, in_story_node_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM journey_story_node
	WHERE story_node_id = ANY(in_story_node_ids)
	AND account_id IN (
		SELECT id
		FROM accounts a
		WHERE a.user = in_player_id
	);

	DELETE FROM journey_story_node_cooldown
	WHERE story_node_id = ANY(in_story_node_ids)
	AND account_id IN (
		SELECT id
		FROM accounts a
		WHERE a.user = in_player_id
	);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58221
f
delete_journey_story_nodes_for_player_account
in_account_id bigint, in_story_node_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_journey_story_nodes_for_player_account(in_account_id bigint, in_story_node_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM journey_story_node
	WHERE story_node_id = ANY(in_story_node_ids)
	AND account_id = in_account_id;

	DELETE FROM journey_story_node_cooldown
	WHERE story_node_id = ANY(in_story_node_ids)
	AND account_id = in_account_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58222
f
delete_map_markers
in_dimension_index integer, in_map_name text, in_player_marker_data dune.deleteplayermarkerdata[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_map_markers(in_dimension_index integer, in_map_name text, in_player_marker_data dune.deleteplayermarkerdata[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	WITH player_hash_ids AS (SELECT player_id, marker_hash_ids FROM UNNEST(in_player_marker_data)),
	player_markers_to_delete AS (
		SELECT player_markers.* FROM player_hash_ids, player_markers JOIN map_names USING(map_name_id)
		WHERE (player_markers.dimension_index = in_dimension_index OR player_markers.dimension_index = -1)
			AND map_names.map_name = in_map_name
			AND player_markers.player_id = player_hash_ids.player_id
			AND player_markers.marker_hash_id = ANY(player_hash_ids.marker_hash_ids)
		ORDER BY player_markers.player_id, player_markers.marker_hash_id, player_markers.dimension_index FOR UPDATE -- Ordering for deadlock avoidance
	)
	DELETE FROM player_markers USING player_markers_to_delete
	WHERE player_markers.dimension_index = player_markers_to_delete.dimension_index
		AND player_markers.map_name_id = player_markers_to_delete.map_name_id
		AND player_markers.player_id = player_markers_to_delete.player_id
		AND player_markers.marker_hash_id = player_markers_to_delete.marker_hash_id;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58223
f
delete_markers_by_id
in_marker_ids integer[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_markers_by_id(in_marker_ids integer[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM markers WHERE marker_hash_id = ANY (in_marker_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58224
f
delete_markers_by_static_location_key
p_location_key text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_markers_by_static_location_key(p_location_key text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM markers WHERE (payload #>> '{LocationKey}') = p_location_key;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58225
f
delete_markers_for_all_players
in_marker_types_to_keep text[], in_map text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_markers_for_all_players(in_marker_types_to_keep text[], in_map text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	-- Lock markers matching query
	WITH affected_markers AS (
		SELECT * FROM markers JOIN map_names USING(map_name_id)
		WHERE (in_map IS NULL OR map_names.map_name = in_map) AND NOT (marker).marker_type = ANY(in_marker_types_to_keep)
		ORDER BY marker_hash_id, dimension_index, map_name_id FOR UPDATE -- Ordering to avoid deadlocks
	),
	-- Lock player_markers to be deleted on cascade
	referencing_player_markers AS (
		SELECT player_markers.* FROM player_markers, affected_markers
		WHERE affected_markers.marker_hash_id = player_markers.marker_hash_id
			AND affected_markers.dimension_index = player_markers.dimension_index
			AND affected_markers.map_name_id = player_markers.map_name_id
		ORDER BY player_id, player_markers.marker_hash_id, player_markers.dimension_index, player_markers.map_name_id FOR UPDATE -- Ordering to avoid deadlocks
	)
	-- Delete markers
	DELETE FROM markers USING affected_markers
	WHERE affected_markers.marker_hash_id = markers.marker_hash_id
		AND affected_markers.dimension_index = markers.dimension_index
		AND affected_markers.map_name_id = markers.map_name_id;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58226
f
delete_markers_return_actor_ids
in_dimension_index integer, in_map_name text, in_marker_ids integer[]
TABLE(actor_id bigint, marker_id integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_markers_return_actor_ids(in_dimension_index integer, in_map_name text, in_marker_ids integer[])
 RETURNS TABLE(actor_id bigint, marker_id integer)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	-- Lock markers matching query
	WITH affected_markers AS (
		SELECT * FROM markers JOIN map_names USING(map_name_id)
		WHERE (dimension_index = in_dimension_index OR dimension_index = -1)
			AND map_names.map_name = in_map_name
			AND marker_hash_id = ANY(in_marker_ids)
		ORDER BY marker_hash_id, dimension_index FOR UPDATE -- Ordering to avoid deadlocks
	),
	-- Lock player_markers to be deleted on cascade
	referencing_player_markers AS (
		SELECT player_id, player_markers.marker_hash_id FROM player_markers, affected_markers
		WHERE affected_markers.marker_hash_id = player_markers.marker_hash_id
			AND affected_markers.dimension_index = player_markers.dimension_index
			AND affected_markers.map_name_id = player_markers.map_name_id
		ORDER BY player_id, player_markers.marker_hash_id, player_markers.dimension_index FOR UPDATE -- Ordering to avoid deadlocks
	),
	-- Delete markers
	deleted_markers AS (
		DELETE FROM markers USING affected_markers
		WHERE affected_markers.marker_hash_id = markers.marker_hash_id
			AND affected_markers.dimension_index = markers.dimension_index
			AND affected_markers.map_name_id = markers.map_name_id
	)
	SELECT * FROM referencing_player_markers;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58227
f
delete_mnemonic_recall_lesson
in_account_id bigint, in_lesson_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_mnemonic_recall_lesson(in_account_id bigint, in_lesson_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM mnemonic_recall WHERE account_id = in_account_id AND lesson_id = in_lesson_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58228
f
delete_mnemonic_recall_lesson_all
in_account_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_mnemonic_recall_lesson_all(in_account_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM mnemonic_recall WHERE account_id = in_account_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58229
f
delete_server_player_access_codes
in_account_id bigint, in_access_code integer, in_access_code_type integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_server_player_access_codes(in_account_id bigint, in_access_code integer, in_access_code_type integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM player_access_codes
		WHERE account_id = in_account_id
		AND access_code = in_access_code
		AND access_code_type = in_access_code_type;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58230
f
delete_spawner
in_map text, in_name text, in_dimension_index integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_spawner(in_map text, in_name text, in_dimension_index integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM actor_spawners WHERE map = in_map AND name = in_name AND dimension_index = in_dimension_index;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58231
f
delete_static_location_markers
p_location_keys text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_static_location_markers(p_location_keys text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM markers WHERE (payload #>> '{LocationKey}') = ANY(p_location_keys);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58232
f
delete_world_partition_by_map_id
in_map_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.delete_world_partition_by_map_id(in_map_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM world_partition WHERE "map"=in_map_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58233
f
demote_guild_member
in_guild_id bigint, in_player_id bigint, in_new_role smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.demote_guild_member(in_guild_id bigint, in_player_id bigint, in_new_role smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	-- check if new admin is actualy in guild
	IF NOT EXISTS(SELECT FROM guild_members WHERE player_id = in_player_id AND guild_id = in_guild_id) THEN
    	RAISE EXCEPTION 'Trying to demote player not in guild %.', in_player_id;
	END IF;

	IF is_player_guild_admin(in_player_id, in_guild_id) THEN
		RAISE EXCEPTION 'Trying to demote admin. promote a member to admin instead.';
	END IF;

	if in_new_role = 100 THEN
		RAISE EXCEPTION 'Trying to demote to admin.';
	END IF;

	-- set new player to new role
	UPDATE guild_members SET role_id = in_new_role WHERE player_id = in_player_id AND guild_id = in_guild_id;

	PERFORM pg_notify('guild_notify_channel', format('demote_player#{"PlayerId" : %s , "GuildId" : %s, "NewRole" : %s}', in_player_id, in_guild_id, in_new_role));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58234
f
determine_partition_label
in_map text, in_dimension_index integer, in_label text, in_allow_overwrite boolean, in_partition_id bigint
text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.determine_partition_label(in_map text, in_dimension_index integer, in_label text DEFAULT NULL::text, in_allow_overwrite boolean DEFAULT true, in_partition_id bigint DEFAULT NULL::bigint)
 RETURNS text
 LANGUAGE plpgsql
AS $function$
DECLARE
	result_label TEXT := in_label;
	tmp_count INTEGER;
	tmp_box_max_x TEXT;
	tmp_box_max_y TEXT;
	tmp_box_min_x TEXT;
	tmp_box_min_y TEXT;
BEGIN
	-- If label is provided and we don't want to overwrite, return it
	IF result_label IS NOT NULL AND in_allow_overwrite = FALSE THEN
		RETURN result_label;
	END IF;

	CASE in_map
		WHEN 'SH_HarkoVillage' THEN return 'HarkoVillage' || '_' || in_dimension_index;
		WHEN 'SH_Arrakeen' THEN return 'Arrakeen' || '_' || in_dimension_index;
		WHEN 'SH_FallenLight' THEN return 'FallenLight' || '_' || in_dimension_index;
		WHEN 'CB_Story_Hephaestus' THEN return 'WreckOfHephaestus' || '_' || in_dimension_index;
		WHEN 'CB_Story_Ecolab_Carthag' THEN return 'BeneathCarthag' || '_' || in_dimension_index;
		WHEN 'CB_SurvivalChallenge_Station_15' THEN return 'Station15' || '_' || in_dimension_index;
		WHEN 'CB_Story_WaterFatManor' THEN return 'WaterFat' || '_' || in_dimension_index;
		WHEN 'Story_ProcesVerbal' THEN return 'ProcesVerbal' || '_' || in_dimension_index;
		WHEN 'DLC_Story_LostHarvest' THEN return 'LostHarvest' || '_' || in_dimension_index;
		WHEN 'DLC_Story_LostHarvest_EcolabA' THEN return 'LostHarvest_EcolabA' || '_' || in_dimension_index;
		WHEN 'DLC_Story_LostHarvest_EcolabB' THEN return 'LostHarvest_EcolabB' || '_' || in_dimension_index;
		WHEN 'DLC_Story_LostHarvest_ForgottenLab' THEN return 'LostHarvest_ForgottenLab' || '_' || in_dimension_index;
		WHEN 'Story_ArtOfKanly' THEN return 'ArtOfKanly' || '_' || in_dimension_index;
		WHEN 'Story_HeighlinerDungeon' THEN return 'HeighlinerDungeon' || '_' || in_dimension_index;
		WHEN 'CB_Dungeon_Hephaestus' THEN return 'WreckOfHephaestusDungeon' || '_' || in_dimension_index;
		WHEN 'CB_Dungeon_OldCarthag' THEN return 'OldCarthagDungeon' || '_' || in_dimension_index;
		WHEN 'CB_Story_BanditFortress01' THEN return 'SandfliesFortress' || '_' || in_dimension_index;
		WHEN 'CB_Overland_S_05' THEN return 'ClosedOffTestingStationIsland' || '_' || in_dimension_index;
		WHEN 'CB_Overland_S_06' THEN return 'GroundVehicleTimeTrialIsland' || '_' || in_dimension_index;
		WHEN 'CB_Overland_S_04' THEN return 'ErythriteCaveIsland' || '_' || in_dimension_index;
		WHEN 'CB_Overland_M_01' THEN return 'RadioactiveShipwreck' || '_' || in_dimension_index;
		WHEN 'Story_Faction_Outpost_Hark' THEN return 'Story_Faction_Outpost_Hark' || '_' || in_dimension_index;
		WHEN 'Story_Faction_Outpost_Atre' THEN return 'Story_Faction_Outpost_Atre' || '_' || in_dimension_index;
		WHEN 'CB_Ecolab_Bronze_Green_089' THEN return 'RadiationDungeon' || '_' || in_dimension_index;
		WHEN 'CB_Ecolab_Bronze_Green_152' THEN return 'ElectricityDungeon' || '_' || in_dimension_index;
		WHEN 'CB_Ecolab_Bronze_Green_195' THEN return 'PoisonDungeon' || '_' || in_dimension_index;
		WHEN 'CB_Ecolab_Bronze_Green_024' THEN return 'DarknessDungeon' || '_' || in_dimension_index;
		WHEN 'CB_Ecolab_Bronze_Green_136' THEN return 'FireDungeon' || '_' || in_dimension_index;
		WHEN 'CB_Overland_S_07' THEN return 'TheRuinsOfTsimpo' || '_' || in_dimension_index;
		WHEN 'CB_Story_DestroyedZanovar' THEN return 'DestroyedZanovar' || '_' || in_dimension_index;
		WHEN 'CB_Story_OrbitalMonitor' THEN return 'OrbitalMonitor' || '_' || in_dimension_index;
		WHEN 'CB_Dungeon_TheFacility' THEN return 'FacilityDungeon' || '_' || in_dimension_index;
		WHEN 'CB_Dungeon_ThePit' THEN return 'PitDungeon' || '_' || in_dimension_index;
		WHEN 'Overmap' THEN
			IF in_dimension_index = 0 THEN
				return 'Overland';
			END IF;
		WHEN 'Survival_1' THEN
			CASE in_dimension_index
				WHEN 0 THEN return 'Abbir';
				WHEN 1 THEN return 'Alraab';
				WHEN 2 THEN return 'Barkan';
				WHEN 3 THEN return 'Coanua';
				WHEN 4 THEN return 'Fajr Kulon';
				WHEN 5 THEN return 'Gara';
				WHEN 6 THEN return 'Hajar';
				WHEN 7 THEN return 'Jacurutu';
				WHEN 8 THEN return 'Kathib';
				WHEN 9 THEN return 'Legg';
				WHEN 10 THEN return 'Makab';
				WHEN 11 THEN return 'Nadir';
				WHEN 12 THEN return 'Ramal';
				WHEN 13 THEN return 'Rifana';
				WHEN 14 THEN return 'Sandrat';
				WHEN 15 THEN return 'Saajid';
				WHEN 16 THEN return 'Tabr Sink';
				WHEN 17 THEN return 'Tharwa';
				WHEN 18 THEN return 'Umbu';
				WHEN 19 THEN return 'Yaracuwan';
				WHEN 20 THEN return 'al-Mut';
				WHEN 21 THEN return 'Altuyur';
				WHEN 22 THEN return 'Ammit';
				WHEN 23 THEN return 'Ashia';
				WHEN 24 THEN return 'Eaqrab';
				WHEN 25 THEN return 'Hagga';
				WHEN 26 THEN return 'Hua';
				WHEN 27 THEN return 'Katal';
				WHEN 28 THEN return 'Khafash';
				WHEN 29 THEN return 'Matar';
				WHEN 30 THEN return 'Rabie';
				WHEN 31 THEN return 'Rajifiri';
				WHEN 32 THEN return 'Remmel';
				WHEN 33 THEN return 'Sahr';
				WHEN 34 THEN return 'Saqer';
				WHEN 35 THEN return 'Ta''lab';
				WHEN 36 THEN return 'Tarl';
				WHEN 37 THEN return 'Tasmin Sink';
				WHEN 38 THEN return 'Thueban';
				WHEN 39 THEN return 'Tuono';
            ELSE
                return 'Survival' || '_' || in_dimension_index;
			END CASE;
		ELSE
			-- Do nothing
	END CASE;

	-- DeepDesert per-partition handling: if all 9 partitions exist, assign based on ordering
	IF in_map = 'DeepDesert_1' THEN
		SELECT count(*) INTO tmp_count FROM world_partition WHERE map = 'DeepDesert_1' AND dimension_index = in_dimension_index;
		IF tmp_count = 9 AND in_partition_id IS NOT NULL THEN
			RETURN (
				WITH chess_notation AS (
					SELECT label, ROW_NUMBER() OVER (ORDER BY null) AS row_num
					FROM (select * from UNNEST(ARRAY['A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3']) as label) as label_list
				),
				partitions AS (
					SELECT partition_id, ROW_NUMBER() OVER (
						order by
							coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'max_x',
							coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'max_y',
							coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'min_x',
							coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'min_y'
						) AS row_num
					FROM world_partition
					where map = 'DeepDesert_1' AND dimension_index = in_dimension_index
				)
				SELECT 'DeepDesert_' || cn.label
				FROM chess_notation cn
				JOIN partitions p ON cn.row_num = p.row_num
				WHERE p.partition_id = in_partition_id
				LIMIT 1
			);
		END IF;
	END IF;

	IF in_partition_id IS NOT NULL THEN
		SELECT coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'max_x', coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'max_y', coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'min_x', coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'min_y'
			INTO tmp_box_max_x, tmp_box_max_y, tmp_box_min_x, tmp_box_min_y
		FROM world_partition
		WHERE partition_id = in_partition_id
		LIMIT 1;

		IF tmp_box_max_x IS NOT NULL AND tmp_box_max_x IN ('1.0','1') AND tmp_box_max_y IN ('1.0','1') AND tmp_box_min_x IN ('0.0','0') AND tmp_box_min_y IN ('0.0','0') THEN
			RETURN upgrade_map_name(in_map) || '_' || in_dimension_index;
		END IF;
	ELSE
		-- If we don't have a partition id, try to read any partition for that map/dimension
		SELECT coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'max_x', coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'max_y', coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'min_x', coalesce(partition_definition->'box', partition_definition->'boxes'->0)->>'min_y'
			INTO tmp_box_max_x, tmp_box_max_y, tmp_box_min_x, tmp_box_min_y
		FROM world_partition
		WHERE map = in_map AND dimension_index = in_dimension_index
		LIMIT 1;

		IF tmp_box_max_x IS NOT NULL AND tmp_box_max_x IN ('1.0','1') AND tmp_box_max_y IN ('1.0','1') AND tmp_box_min_x IN ('0.0','0') AND tmp_box_min_y IN ('0.0','0') THEN
			RETURN upgrade_map_name(in_map) || '_' || in_dimension_index;
		END IF;
	END IF;

	RETURN NULL;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58236
f
determine_partition_label_trigger

trigger

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.determine_partition_label_trigger()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF NEW.label IS NULL THEN
		UPDATE world_partition
		SET label = determine_partition_label(NEW.map, NEW.dimension_index, NULL, false)
		WHERE partition_id = NEW.partition_id;
	END IF;

	RETURN NULL; -- AFTER ROW trigger does not need to return a row
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58237
f
disband_guild
in_guild_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.disband_guild(in_guild_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_guild_name TEXT;
	members_list BIGINT[];
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	-- check if guild exists
	SELECT guild_name INTO out_guild_name FROM guilds WHERE guild_id = in_guild_id;
	IF NOT FOUND THEN
    	RAISE EXCEPTION 'Trying to disband non existing guild %.', in_guild_id;
	END IF;

	-- get members list
	members_list := ARRAY(SELECT player_id FROM guild_members WHERE guild_id = in_guild_id);

	-- delete
	DELETE FROM guilds WHERE guild_id = in_guild_id;

	PERFORM pg_notify('guild_notify_channel', format('guild_disband#{"GuildId" : %s , "GuildName" : "%s", "PlayerIds" : %s}', in_guild_id, out_guild_name, to_json(members_list)::text));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58238
f
disband_party
in_party_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.disband_party(in_party_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	invite_ids BIGINT[];
	sender_ids BIGINT[];
	player_ids BIGINT[];
BEGIN
	PERFORM parties_get_exclusive_operation_lock();

	DELETE FROM party_members WHERE party_id = in_party_id;
	DELETE FROM parties WHERE party_id = in_party_id;
	DELETE FROM platform_parties_mapping WHERE dune_party_id = in_party_id;

	WITH removed_invites AS (
		DELETE FROM party_invites
		WHERE party_id = in_party_id
		RETURNING *
	) SELECT array_agg(invite_id), array_agg(sender_player_id), array_agg(player_id) from removed_invites INTO invite_ids, sender_ids, player_ids;

	PERFORM pg_notify('party_notify_channel', format('disband_party#{"PartyId" : %s, "InviteIds" : [%s] ,  "SenderIds" : [%s] , "PlayerIds" : [%s]}',
		in_party_id, ARRAY_TO_STRING(invite_ids, ','), ARRAY_TO_STRING(sender_ids, ','), ARRAY_TO_STRING(player_ids, ',')));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58239
f
downgrade_map_name
in_map_name text
text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.downgrade_map_name(in_map_name text)
 RETURNS text
 LANGUAGE plpgsql
AS $function$
BEGIN
	return
		CASE in_map_name
			WHEN 'HaggaBasin' THEN 'Survival_1'
			WHEN 'HarkoVillage' THEN 'SH_HarkoVillage'
			WHEN 'DeepDesert' THEN 'DeepDesert_1'
			WHEN 'Overland' THEN 'Overmap'
			WHEN 'Arrakeen' THEN 'SH_Arrakeen'
			WHEN 'WreckOfHephaestus' THEN 'CB_Story_Hephaestus'
			WHEN 'BeneathCarthag' THEN 'CB_Story_Ecolab_Carthag'
			WHEN 'Station15' THEN 'CB_SurvivalChallenge_Station_15'
			WHEN 'WaterFat' THEN 'CB_Story_WaterFatManor'
			WHEN 'FallenLight' THEN 'SH_FallenLight'
			WHEN 'ProcesVerbal' THEN 'Story_ProcesVerbal'
			WHEN 'LostHarvest' THEN 'DLC_Story_LostHarvest'
			WHEN 'LostHarvest_EcolabA' THEN 'DLC_Story_LostHarvest_EcolabA'
			WHEN 'LostHarvest_EcolabB' THEN 'DLC_Story_LostHarvest_EcolabB'
			WHEN 'LostHarvest_ForgottenLab' THEN 'DLC_Story_LostHarvest_ForgottenLab'
			WHEN 'ArtOfKanly' THEN 'Story_ArtOfKanly'
			WHEN 'HeighlinerDungeon' THEN 'Story_HeighlinerDungeon'
			WHEN 'WreckOfHephaestusDungeon' THEN 'CB_Dungeon_Hephaestus'
			WHEN 'OldCarthagDungeon' THEN 'CB_Dungeon_OldCarthag'
			WHEN 'SandfliesFortress' THEN 'CB_Story_BanditFortress01'
			WHEN 'ClosedOffTestingStationIsland' THEN 'CB_Overland_S_05'
			WHEN 'GroundVehicleTimeTrialIsland' THEN 'CB_Overland_S_06'
			WHEN 'ErythriteCaveIsland' THEN 'CB_Overland_S_04'
			WHEN 'RadioactiveShipwreck' THEN 'CB_Overland_M_01'
			WHEN 'TheRuinsOfTsimpo' THEN 'CB_Overland_S_07'
			WHEN 'Story_Faction_Outpost_Hark' THEN 'Story_Faction_Outpost_Hark'
			WHEN 'Story_Faction_Outpost_Atre' THEN 'Story_Faction_Outpost_Atre'
            WHEN 'RadiationDungeon' THEN 'CB_Ecolab_Bronze_Green_089'
			WHEN 'ElectricityDungeon' THEN 'CB_Ecolab_Bronze_Green_152'
			WHEN 'PoisonDungeon' THEN 'CB_Ecolab_Bronze_Green_195'
			WHEN 'DarknessDungeon' THEN 'CB_Ecolab_Bronze_Green_024'
			WHEN 'FireDungeon' THEN 'CB_Ecolab_Bronze_Green_136'
			WHEN 'DestroyedZanovar' THEN 'CB_Story_DestroyedZanovar'
			WHEN 'OrbitalMonitor' THEN 'CB_Story_OrbitalMonitor'
			WHEN 'FacilityDungeon' THEN 'CB_Dungeon_TheFacility'
			WHEN 'PitDungeon' THEN 'CB_Dungeon_ThePit'
			WHEN 'WindPass' THEN 'CB_Overland_S_08'
			ELSE in_map_name
		END;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58240
f
drain_item_tracking_data

TABLE(function_name dune.itemtrackingfunctiontype, item_id bigint, account_id bigint, inventory_id bigint, template_id text, event_time timestamp without time zone, position_index bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.drain_item_tracking_data()
 RETURNS TABLE(function_name dune.itemtrackingfunctiontype, item_id bigint, account_id bigint, inventory_id bigint, template_id text, event_time timestamp without time zone, position_index bigint)
 LANGUAGE sql
AS $function$
    DELETE FROM item_operations_staging_table
    RETURNING
        function_name,
        item_id,
        account_id,
        inventory_id,
        template_id,
        event_time AT TIME ZONE 'UTC',
        position_index;
$function$

@@@ENDRTN@@@
@@@RTN@@@58241
f
dune_exchange_add_sell_order
in_exchange_id bigint, in_access_point_id bigint, in_owner_id bigint, in_max_orders_per_player integer, in_expiration_time bigint, in_item_id bigint, in_count bigint, in_category_mask integer, in_category_depth smallint, in_durability_cur real, in_durability_max real, in_item_price bigint, in_wear_normalized_item_price bigint, in_quality_level bigint, in_solari_cost bigint
dune.duneexchangeaddsellorderresult

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_add_sell_order(in_exchange_id bigint, in_access_point_id bigint, in_owner_id bigint, in_max_orders_per_player integer, in_expiration_time bigint, in_item_id bigint, in_count bigint, in_category_mask integer, in_category_depth smallint, in_durability_cur real, in_durability_max real, in_item_price bigint, in_wear_normalized_item_price bigint, in_quality_level bigint, in_solari_cost bigint)
 RETURNS dune.duneexchangeaddsellorderresult
 LANGUAGE plpgsql
AS $function$
DECLARE
	exchange_inventory_id BIGINT;
	user_id BIGINT;
	new_item_id BIGINT;
	item_template_id TEXT;
	result DuneExchangeAddSellOrderResult;
	fls_id TEXT;
BEGIN
	result.order_id = 0;
	SELECT INTO result.order_slots_used get_dune_exchange_used_order_slots(in_owner_id);

	IF result.order_slots_used >= in_max_orders_per_player THEN
		RETURN result;
	END IF;

	PERFORM * from dune_exchange_orders where item_id = in_item_id;
	IF FOUND THEN
	    -- Debug logging
        SELECT acc."user"
            INTO fls_id
            FROM accounts acc
			JOIN player_state ps on ps.account_id = acc.id
			WHERE ps.player_controller_id = in_owner_id
            LIMIT 1;
		PERFORM log_cheating(fls_id, 'exchange_order_dupe');
        RAISE WARNING 'Trying to dupe exchange sell orders FLS: %', fls_id;
		RETURN result;
	END IF;

	SELECT INTO user_id dune_exchange_get_user_id(in_owner_id);
	SELECT INTO exchange_inventory_id get_exchange_inventory_id(in_exchange_id);

	IF exchange_inventory_id IS NULL THEN
		RETURN result;
	END IF;

	UPDATE dune_exchange_users SET solari_balance = solari_balance - in_solari_cost WHERE id = user_id AND solari_balance >= in_solari_cost;

	IF NOT FOUND THEN
		RETURN result;
	END IF;

	SELECT INTO STRICT item_template_id template_id FROM items WHERE id = in_item_id FOR UPDATE;
	INSERT INTO dune_exchange_orders(exchange_id, access_point_id, owner_id, expiration_time, template_id, durability_cur, durability_max, category_mask, category_depth, item_price, quality_level)
	VALUES(in_exchange_id, in_access_point_id, in_owner_id, in_expiration_time, item_template_id, in_durability_cur, in_durability_max, in_category_mask, in_category_depth, in_item_price, in_quality_level)
	RETURNING id INTO result.order_id;

	INSERT INTO dune_exchange_sell_orders(order_id, initial_stack_size, wear_normalized_price) VALUES(result.order_id, in_count, in_wear_normalized_item_price);
	SELECT INTO new_item_id move_inventory_item(in_item_id, exchange_inventory_id, result.order_id, in_count);

	IF new_item_id IS NULL THEN
		RAISE EXCEPTION 'Failed to move inventory item %', in_item_id;
	END IF;

	UPDATE dune_exchange_orders SET item_id = new_item_id WHERE id = result.order_id;

	result.order_slots_used = result.order_slots_used + 1;

	RETURN result;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58242
f
dune_exchange_cancel_order
in_order_id bigint, in_purge_time bigint, in_completion_type integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_cancel_order(in_order_id bigint, in_purge_time bigint, in_completion_type integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
	DELETE FROM dune_exchange_sell_orders WHERE order_id = in_order_id;
	IF NOT FOUND THEN
		RETURN;
	END IF;

	WITH item_stack_size AS
		(SELECT item.stack_size
			FROM dune_exchange_orders ord
			JOIN items item ON ord.item_id = item.id
			WHERE ord.id = in_order_id)
		INSERT INTO dune_exchange_fulfilled_orders(order_id, completion_type, stack_size, original_order_id) VALUES(in_order_id, in_completion_type, (SELECT * FROM item_stack_size), in_order_id);

	UPDATE dune_exchange_orders SET expiration_time = in_purge_time, revision = revision + 1 WHERE id = in_order_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58243
f
dune_exchange_expire_orders
in_exchange_id bigint, in_current_time bigint, in_purge_time bigint, in_expired_completion_type integer
SETOF dune.exchangeexpiredorder

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_expire_orders(in_exchange_id bigint, in_current_time bigint, in_purge_time bigint, in_expired_completion_type integer)
 RETURNS SETOF dune.exchangeexpiredorder
 LANGUAGE plpgsql
AS $function$
DECLARE
	cur_order RECORD;
	order_is_npc_order BOOLEAN;
BEGIN
	FOR cur_order IN
		SELECT 
			ord.id AS order_id,
			ord.owner_id AS owner_id,
			ord.is_npc_order AS is_npc_order,
			item.stack_size AS stack_size,
			ord.item_price AS item_price,
            ord.quality_level AS quality_level
		FROM dune_exchange_orders ord
		JOIN dune_exchange_sell_orders sord ON (ord.id = sord.order_id)
		JOIN items item ON (ord.item_id = item.id)
		WHERE ord.exchange_id = in_exchange_id AND ord.expiration_time IS NOT NULL AND in_current_time >= ord.expiration_time
		FOR UPDATE
	LOOP
		IF NOT cur_order.is_npc_order THEN
			-- Make an item_storage record for the order item.
			DELETE FROM dune_exchange_sell_orders WHERE order_id = cur_order.order_id;
			INSERT INTO dune_exchange_fulfilled_orders(order_id, completion_type, stack_size, original_order_id) VALUES(cur_order.order_id, in_expired_completion_type, cur_order.stack_size, cur_order.order_id);
			UPDATE dune_exchange_orders SET revision = revision + 1, expiration_time = in_purge_time WHERE id = cur_order.order_id;

			RETURN NEXT (
				cur_order.order_id,
				cur_order.owner_id,
				in_expired_completion_type,
				cur_order.stack_size,
				cur_order.item_price,
				cur_order.order_id,
                cur_order.quality_level);
		ELSE
			-- Delete the order. Will cascade into the items table.
			DELETE FROM dune_exchange_orders WHERE id = order_id;
		END IF;

	END LOOP;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58244
f
dune_exchange_fulfill_sell_order
in_exchange_id bigint, in_max_orders_per_player integer, in_purchased_completion_type integer, in_sold_completion_type integer, in_instigator_id bigint, in_order_id bigint, in_order_revision bigint, in_dst_inventory_id bigint, in_dst_index bigint, in_count bigint, in_solaris_fee bigint, in_purge_time bigint
dune.duneexchangefulfillsellorderresult

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_fulfill_sell_order(in_exchange_id bigint, in_max_orders_per_player integer, in_purchased_completion_type integer, in_sold_completion_type integer, in_instigator_id bigint, in_order_id bigint, in_order_revision bigint, in_dst_inventory_id bigint, in_dst_index bigint, in_count bigint, in_solaris_fee bigint, in_purge_time bigint)
 RETURNS dune.duneexchangefulfillsellorderresult
 LANGUAGE plpgsql
AS $function$
DECLARE
	exchange_inventory_id BIGINT;
	order_access_point_id BIGINT;
	seller_actor_id BIGINT;
	seller_user_id BIGINT;
	buyer_user_id BIGINT;
	user_solari_balance BIGINT;
	per_item_price BIGINT;
	total_cost BIGINT;
	order_revision BIGINT;
	order_item_id BIGINT;
	order_is_npc_order BOOLEAN;
	order_category_mask INT;
	order_category_depth SMALLINT;
	item_template_id TEXT;
	item_durability_cur REAL;
	item_durability_max REAL;
	storage_order_id BIGINT;
	log_order_id BIGINT;
	result DuneExchangeFulfillSellOrderResult;

BEGIN
	result.item_id = 0;
	SELECT INTO result.order_slots_used get_dune_exchange_used_order_slots(in_instigator_id);

	IF result.order_slots_used >= in_max_orders_per_player THEN
		RETURN result;
	END IF;

	SELECT INTO exchange_inventory_id get_exchange_inventory_id(in_exchange_id);
	SELECT INTO buyer_user_id dune_exchange_get_user_id(in_instigator_id);

	BEGIN
		SELECT INTO STRICT user_solari_balance solari_balance FROM dune_exchange_users WHERE id = buyer_user_id FOR UPDATE;
		SELECT INTO STRICT
			order_revision,
			order_access_point_id,
			order_item_id,
			item_template_id,
			item_durability_cur,
			item_durability_max,
			seller_actor_id,
			order_category_mask,
			order_category_depth,
			order_is_npc_order,
			per_item_price

			ord.revision,
			ord.access_point_id,
			ord.item_id,
			ord.template_id,
			ord.durability_cur,
			ord.durability_max,
			ord.owner_id,
			ord.category_mask,
			ord.category_depth,
			ord.is_npc_order,
			ord.item_price
		FROM
			dune_exchange_orders ord
		JOIN dune_exchange_sell_orders sord ON (ord.id = sord.order_id)
		WHERE
			id = in_order_id AND revision = in_order_revision
		FOR UPDATE;
	EXCEPTION
		WHEN NO_DATA_FOUND THEN RETURN result;
	END;

	IF order_revision != in_order_revision THEN
		RETURN result;
	END IF;

	IF NOT order_is_npc_order THEN
		SELECT INTO seller_user_id dune_exchange_get_user_id(seller_actor_id);
	END IF;
	total_cost = per_item_price * in_count + in_solaris_fee;

	IF total_cost > user_solari_balance THEN
		RETURN result;
	END IF;

	IF in_dst_inventory_id IS NULL THEN
		-- Item is to be transferred to exchange storage rather than an external inventory. Make a record for it.
		INSERT INTO dune_exchange_orders(exchange_id, access_point_id, owner_id, template_id, expiration_time, durability_cur, durability_max, item_price, category_mask, category_depth)
			VALUES(in_exchange_id, order_access_point_id, in_instigator_id, item_template_id, in_purge_time, item_durability_cur, item_durability_max, per_item_price, order_category_mask, order_category_depth)
			RETURNING id INTO storage_order_id;

		INSERT INTO dune_exchange_fulfilled_orders(order_id, completion_type, stack_size, original_order_id)
			VALUES(storage_order_id, in_purchased_completion_type, in_count, in_order_id);

		in_dst_inventory_id = exchange_inventory_id;
		in_dst_index = storage_order_id;
	END IF;

	UPDATE dune_exchange_orders SET item_id = NULL WHERE id = in_order_id;

	SELECT INTO result.item_id move_inventory_item(order_item_id, in_dst_inventory_id, in_dst_index, in_count);

	IF result.item_id IS NULL THEN
		IF storage_order_id IS NOT NULL THEN
			DELETE FROM dune_exchange_orders WHERE id = storage_order_id;
		END IF;
		RETURN result;
	END IF;

	-- Create an entry for the fulfilled orders log.
	SELECT INTO log_order_id order_id FROM dune_exchange_fulfilled_orders where source_order_id = in_order_id FOR SHARE;

	IF log_order_id IS NOT NULL THEN
		UPDATE dune_exchange_orders SET expiration_time = in_purge_time, revision = revision + 1 WHERE id = log_order_id;
		UPDATE dune_exchange_fulfilled_orders SET stack_size = stack_size + in_count WHERE order_id = log_order_id;
	ELSE
		INSERT INTO dune_exchange_orders(exchange_id, access_point_id, owner_id, template_id, expiration_time, durability_cur, durability_max, item_price, category_mask, category_depth)
			VALUES(in_exchange_id, order_access_point_id, seller_actor_id, item_template_id, in_purge_time, item_durability_cur, item_durability_max, per_item_price, order_category_mask, order_category_depth) RETURNING id INTO log_order_id;

		INSERT INTO dune_exchange_fulfilled_orders(order_id, source_order_id, completion_type, stack_size, original_order_id)
			VALUES(log_order_id, in_order_id, in_sold_completion_type, in_count, in_order_id);
	END IF;

	UPDATE dune_exchange_users SET solari_balance = solari_balance - total_cost WHERE id = buyer_user_id;
	-- If the new ID is equal to the old ID the item/stack was moved rather than split and the order is now empty
	IF result.item_id = order_item_id THEN
		DELETE FROM dune_exchange_orders WHERE id = in_order_id;
	ELSE
		UPDATE dune_exchange_orders SET item_id = order_item_id, revision = revision + 1 WHERE id = in_order_id; -- Item was split. Restore reference to remaining items.
	END IF;
	-- If the item was transferred to exchange storage rather than an external inventory, update the record with the item ID.
	IF storage_order_id IS NOT NULL THEN
		UPDATE dune_exchange_orders SET item_id = result.item_id WHERE id = storage_order_id;
	END IF;

	result.order_slots_used = result.order_slots_used + 1;

	RETURN result;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58246
f
dune_exchange_get_item_price_stats
in_template_ids text[]
TABLE(template_id text, minimum bigint, average bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_get_item_price_stats(in_template_ids text[])
 RETURNS TABLE(template_id text, minimum bigint, average bigint)
 LANGUAGE plpgsql
AS $function$
DECLARE
	average_price REAL;
	minimum_price REAL;
BEGIN
	RETURN QUERY SELECT ord.template_id, MIN(sord.wear_normalized_price), CAST(SUM(sord.wear_normalized_price * item.stack_size) / SUM(item.stack_size) AS BIGINT)
	FROM dune_exchange_orders ord
	JOIN items item ON item.id = ord.item_id
	JOIN dune_exchange_sell_orders sord ON sord.order_id = ord.id
	WHERE ord.template_id = ANY(in_template_ids) GROUP BY ord.template_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58247
f
dune_exchange_get_user_id
in_owner_id bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_get_user_id(in_owner_id bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	new_user_id BIGINT;
	user_id BIGINT;
BEGIN
	INSERT INTO dune_exchange_users(owner_id) VALUES(in_owner_id) ON CONFLICT DO NOTHING RETURNING id INTO new_user_id;
	SELECT INTO user_id COALESCE(new_user_id, id) FROM dune_exchange_users WHERE owner_id = in_owner_id;

	return user_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58248
f
dune_exchange_modify_user_solari_balance
in_controller_id bigint, in_solari_delta bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_modify_user_solari_balance(in_controller_id bigint, in_solari_delta bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	user_id BIGINT;
	current_balance BIGINT;
	new_balance BIGINT;
	delta_balance BIGINT;
	fls_id TEXT;
	function_oid oid;
BEGIN
	SELECT INTO user_id dune_exchange_get_user_id(in_controller_id);
	SELECT INTO current_balance balance from player_virtual_currency_balances WHERE currency_id = get_solaris_id() AND player_controller_id = in_controller_id;

	IF current_balance < 0 THEN
		SELECT acc."user"
		INTO fls_id
		FROM accounts acc
		JOIN player_state ps on ps.account_id = acc.id
		WHERE ps.player_controller_id = in_controller_id
		LIMIT 1;

		PERFORM log_cheating(COALESCE(fls_id, in_controller_id::text), 'exchange_negative_solaris');
		UPDATE player_virtual_currency_balances SET balance = 0 WHERE currency_id = get_solaris_id() AND player_controller_id = in_controller_id;
		current_balance = 0;
	END IF;

	delta_balance = in_solari_delta;
	IF current_balance < in_solari_delta THEN
		delta_balance = current_balance;
	END IF;

	UPDATE dune_exchange_users SET solari_balance = solari_balance + delta_balance WHERE id = user_id;
	
	UPDATE player_virtual_currency_balances SET balance = balance - delta_balance WHERE currency_id = get_solaris_id() AND player_controller_id = in_controller_id RETURNING player_virtual_currency_balances.balance INTO new_balance;

	GET DIAGNOSTICS function_oid = PG_ROUTINE_OID;
	PERFORM log_event_solaris(function_oid, 'update_solaris', in_controller_id, new_balance, delta_balance);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58249
f
dune_exchange_purge_completed_orders
in_exchange_id bigint, in_current_time bigint
SETOF dune.exchangeexpiredorder

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_purge_completed_orders(in_exchange_id bigint, in_current_time bigint)
 RETURNS SETOF dune.exchangeexpiredorder
 LANGUAGE plpgsql
AS $function$
DECLARE
	cur_order RECORD;
	order_is_npc_order BOOLEAN;
BEGIN
	FOR cur_order IN
		SELECT 
			ord.id AS order_id,
			ord.owner_id AS owner_id,
			ord.is_npc_order AS is_npc_order,
			sord.stack_size AS stack_size,
			ord.item_price AS item_price,
			sord.completion_type AS completion_type,
			sord.original_order_id AS original_order_id,
            ord.quality_level AS quality_level
		FROM dune_exchange_orders ord
		JOIN dune_exchange_fulfilled_orders sord ON (ord.id = sord.order_id)
		WHERE ord.exchange_id = in_exchange_id AND ord.expiration_time IS NOT NULL AND in_current_time >= ord.expiration_time
		FOR UPDATE
	LOOP
		DELETE FROM dune_exchange_orders WHERE id = cur_order.order_id;
		RETURN NEXT (
			cur_order.order_id,
			cur_order.owner_id,
			cur_order.completion_type,
			cur_order.stack_size,
			cur_order.item_price,
			cur_order.original_order_id,
            cur_order.quality_level);
	END LOOP;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58250
f
dune_exchange_query_storage_item
in_order_id bigint
TABLE(completion_type integer, id bigint, revision bigint, expiration_time bigint, access_point_id bigint, ap_name text, owner_id bigint, item_id bigint, template_id text, stack_size bigint, item_price bigint, quality_level bigint, durability_cur real, durability_max real, dynamic_stats jsonb)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_query_storage_item(in_order_id bigint)
 RETURNS TABLE(completion_type integer, id bigint, revision bigint, expiration_time bigint, access_point_id bigint, ap_name text, owner_id bigint, item_id bigint, template_id text, stack_size bigint, item_price bigint, quality_level bigint, durability_cur real, durability_max real, dynamic_stats jsonb)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
		SELECT sord.completion_type, ord.id, ord.revision, ord.expiration_time, ord.access_point_id, ap.name, ord.owner_id, ord.item_id, ord.template_id, sord.stack_size, ord.item_price, ord.quality_level, ord.durability_cur, ord.durability_max, item.stats
		FROM dune_exchange_orders ord
		JOIN dune_exchange_fulfilled_orders sord ON (ord.id = sord.order_id)
		JOIN dune_exchange_accesspoints ap ON (ord.access_point_id = ap.id)
		LEFT JOIN items item ON ord.item_id = item.id
		WHERE ord.id = in_order_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58251
f
dune_exchange_query_storage_items
in_exchange_id bigint, in_owner_id bigint
TABLE(completion_type integer, id bigint, revision bigint, expiration_time bigint, access_point_id bigint, ap_name text, owner_id bigint, item_id bigint, template_id text, stack_size bigint, item_price bigint, quality_level bigint, durability_cur real, durability_max real, dynamic_stats jsonb)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_query_storage_items(in_exchange_id bigint, in_owner_id bigint)
 RETURNS TABLE(completion_type integer, id bigint, revision bigint, expiration_time bigint, access_point_id bigint, ap_name text, owner_id bigint, item_id bigint, template_id text, stack_size bigint, item_price bigint, quality_level bigint, durability_cur real, durability_max real, dynamic_stats jsonb)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
		SELECT sord.completion_type, ord.id, ord.revision, ord.expiration_time, ord.access_point_id, ap.name, ord.owner_id, ord.item_id, ord.template_id, sord.stack_size, ord.item_price, ord.quality_level, ord.durability_cur, ord.durability_max, item.stats
		FROM dune_exchange_orders ord
		JOIN dune_exchange_fulfilled_orders sord ON (ord.id = sord.order_id)
		JOIN dune_exchange_accesspoints ap ON (ord.access_point_id = ap.id)
		LEFT JOIN items item ON ord.item_id = item.id
		WHERE ord.exchange_id = in_exchange_id AND ord.owner_id = in_owner_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58252
f
dune_exchange_relist_order
in_order_id bigint, in_expiration_time bigint, in_item_price bigint, in_wear_normalized_item_price bigint, in_solari_cost bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_relist_order(in_order_id bigint, in_expiration_time bigint, in_item_price bigint, in_wear_normalized_item_price bigint, in_solari_cost bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	user_id BIGINT;
	initial_stack_size BIGINT;
BEGIN
	WITH order_owner_id AS
		(SELECT ord.owner_id
			FROM dune_exchange_orders ord
			JOIN dune_exchange_fulfilled_orders ford ON (ord.id = ford.order_id)
			WHERE id = in_order_id AND item_id IS NOT NULL)
		SELECT INTO user_id dune_exchange_get_user_id((SELECT * FROM order_owner_id));

	UPDATE dune_exchange_users SET solari_balance = solari_balance - in_solari_cost WHERE id = user_id AND solari_balance >= in_solari_cost;

	IF NOT FOUND THEN
		RETURN 0;
	END IF;

	DELETE FROM dune_exchange_fulfilled_orders WHERE order_id = in_order_id;

	IF NOT FOUND THEN
		RAISE EXCEPTION 'Order % is not a fulfilled order', in_order_id;
	END IF;

	WITH item_stack_size AS
		(SELECT item.stack_size
			FROM dune_exchange_orders ord
			JOIN items item ON ord.item_id = item.id
			WHERE ord.id = in_order_id)
		INSERT INTO dune_exchange_sell_orders(order_id, initial_stack_size, wear_normalized_price) VALUES(in_order_id, (SELECT * FROM item_stack_size), in_wear_normalized_item_price) RETURNING dune_exchange_sell_orders.initial_stack_size INTO initial_stack_size;

	UPDATE dune_exchange_orders SET item_price = in_item_price, expiration_time = in_expiration_time WHERE id = in_order_id;

	RETURN initial_stack_size;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58253
f
dune_exchange_retrieve_solari_balance
in_owner_id bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_retrieve_solari_balance(in_owner_id bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	current_balance BIGINT;
	fls_id TEXT;
BEGIN
	SELECT INTO current_balance solari_balance from dune_exchange_users WHERE owner_id = in_owner_id LIMIT 1;

	IF current_balance < 0 THEN
		SELECT acc."user"
		INTO fls_id
		FROM accounts acc
		JOIN player_state ps on ps.account_id = acc.id
		WHERE ps.player_controller_id = in_owner_id
		LIMIT 1;

		PERFORM log_cheating(COALESCE(fls_id, in_owner_id::text), 'exchange_negative_solaris');

		UPDATE dune_exchange_users SET solari_balance = 0 WHERE owner_id = in_owner_id;
	END IF;
	RETURN (SELECT solari_balance FROM dune_exchange_users WHERE owner_id = in_owner_id LIMIT 1);
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58254
f
dune_exchange_retrieve_solaris_from_item
in_controller_id bigint, in_order_id bigint
dune.duneexchangeretrievesolarisfromitemresult

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_retrieve_solaris_from_item(in_controller_id bigint, in_order_id bigint)
 RETURNS dune.duneexchangeretrievesolarisfromitemresult
 LANGUAGE plpgsql
AS $function$
DECLARE
	result DuneExchangeRetrieveSolarisFromItemResult;
	new_balance BIGINT;
	function_oid oid;
BEGIN
	WITH
		delete_orders_prices AS (
			DELETE FROM dune_exchange_orders
				USING dune_exchange_fulfilled_orders
				WHERE (dune_exchange_orders.id = dune_exchange_fulfilled_orders.order_id)
					AND id = in_order_id AND (item_id IS NULL OR item_id = 0)
			RETURNING item_price * dune_exchange_fulfilled_orders.stack_size AS total_price
		),
		total_price AS (
			SELECT SUM(total_price) AS delta FROM delete_orders_prices
		)
	UPDATE player_virtual_currency_balances 
		SET balance = balance + total_price.delta
		FROM total_price
		WHERE currency_id = get_solaris_id() AND player_controller_id = in_controller_id
		RETURNING
			player_virtual_currency_balances.balance,
            total_price.delta, 
            (SELECT original_order_id FROM dune_exchange_fulfilled_orders WHERE order_id = in_order_id) 
        INTO
			new_balance,
            result.total_item_value,
            result.original_order_id;
	
	GET DIAGNOSTICS function_oid = PG_ROUTINE_OID;
	PERFORM log_event_solaris(function_oid, 'update_solaris', in_controller_id, new_balance, result.total_item_value);

	RETURN result;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58255
f
dune_exchange_retrieve_storage_item
in_exchange_id bigint, in_order_id bigint, in_dst_inventory_id bigint, in_dst_index bigint, in_count bigint
dune.duneexchangeretrievestorageorderresult

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_retrieve_storage_item(in_exchange_id bigint, in_order_id bigint, in_dst_inventory_id bigint, in_dst_index bigint, in_count bigint)
 RETURNS dune.duneexchangeretrievestorageorderresult
 LANGUAGE plpgsql
AS $function$
DECLARE
	exchange_inventory_id BIGINT;
	order_owner_id BIGINT;
	order_item_id BIGINT;
	result DuneExchangeRetrieveStorageOrderResult;
BEGIN
	SELECT INTO exchange_inventory_id get_exchange_inventory_id(in_exchange_id);

	IF exchange_inventory_id IS NULL THEN
		RETURN NULL;
	END IF;

	SELECT INTO STRICT order_owner_id, order_item_id owner_id, item_id
	FROM dune_exchange_orders
	JOIN dune_exchange_fulfilled_orders ON (dune_exchange_orders.id = dune_exchange_fulfilled_orders.order_id)
	WHERE id = in_order_id
	FOR UPDATE;

	UPDATE dune_exchange_orders SET item_id = NULL WHERE id = in_order_id;

	SELECT INTO result.item_id move_inventory_item(order_item_id, in_dst_inventory_id, in_dst_index, in_count);

	IF result.item_id IS NULL THEN
		RAISE EXCEPTION 'Failed to move inventory item % on order %', in_item_id, in_order_id;
	END IF;

    SELECT INTO result.original_order_id original_order_id FROM dune_exchange_fulfilled_orders WHERE order_id = in_order_id;

	-- If the new ID is equal to the old ID the item/stack was moved rather than split and the order is now empty
	IF result.item_id = order_item_id THEN
		DELETE FROM dune_exchange_orders WHERE id = in_order_id;
	ELSE
		UPDATE dune_exchange_orders SET item_id = order_item_id WHERE id = in_order_id; -- Item was split. Restore reference to remaining items.
		UPDATE dune_exchange_fulfilled_orders SET stack_size = stack_size - in_count WHERE order_id = in_order_id;
	END IF;

	SELECT INTO result.order_slots_used get_dune_exchange_used_order_slots(order_owner_id);

	RETURN result;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58256
f
dune_exchange_update_recurring_sell_order
in_exchange_id bigint, in_expiration_time bigint, in_access_point_id bigint, in_owner_id bigint, in_item_id bigint, in_increment bigint, in_max_count bigint, in_category_mask integer, in_category_depth smallint, in_durability_cur real, in_durability_max real, in_item_price bigint, in_wear_normalized_item_price bigint, in_quality_level bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_exchange_update_recurring_sell_order(in_exchange_id bigint, in_expiration_time bigint, in_access_point_id bigint, in_owner_id bigint, in_item_id bigint, in_increment bigint, in_max_count bigint, in_category_mask integer, in_category_depth smallint, in_durability_cur real, in_durability_max real, in_item_price bigint, in_wear_normalized_item_price bigint, in_quality_level bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	exchange_inventory_id BIGINT;
	new_item_id BIGINT;
	item_stack_size BIGINT;
	new_order_id BIGINT;
	item_template_id TEXT;
	old_count BIGINT;
	new_count BIGINT;
	delta_count BIGINT;
BEGIN
	LOCK TABLE dune_exchange_orders, items IN ROW EXCLUSIVE MODE;

	SELECT INTO exchange_inventory_id get_exchange_inventory_id(in_exchange_id);

	IF exchange_inventory_id IS NULL THEN
		RETURN 0;
	END IF;

	SELECT INTO STRICT item_template_id template_id FROM items WHERE id = in_item_id FOR SHARE;

	SELECT INTO new_order_id, new_item_id ord.id, ord.item_id
	FROM dune_exchange_orders ord
	JOIN dune_exchange_sell_orders sord ON (ord.id = sord.order_id)
	WHERE ord.is_npc_order = TRUE AND ord.exchange_id = in_exchange_id AND ord.access_point_id = in_access_point_id AND ord.template_id = item_template_id AND ord.item_price = in_item_price AND ord.quality_level = in_quality_level
	FOR SHARE;

	IF new_order_id IS NULL THEN
		INSERT INTO dune_exchange_orders(exchange_id, access_point_id, owner_id, is_npc_order, expiration_time, template_id, durability_cur, durability_max, category_mask, category_depth, item_price, quality_level)
		VALUES(in_exchange_id, in_access_point_id, in_owner_id, TRUE, in_expiration_time, item_template_id, in_durability_cur, in_durability_max, in_category_mask, in_category_depth, in_item_price, in_quality_level)
		RETURNING id INTO new_order_id;

		INSERT INTO dune_exchange_sell_orders(order_id, initial_stack_size, wear_normalized_price) VALUES(new_order_id, new_count, in_wear_normalized_item_price);
		SELECT INTO new_item_id move_inventory_item(in_item_id, exchange_inventory_id, new_order_id, in_increment);

		IF new_item_id IS NULL THEN
			DELETE FROM dune_exchange_orders WHERE id = new_order_id;
			RETURN 0;
		END IF;

		UPDATE dune_exchange_orders SET item_id = new_item_id WHERE id = new_order_id;

		RETURN in_increment;
	ELSE
		UPDATE dune_exchange_orders SET expiration_time = in_expiration_time WHERE id=new_order_id;
		SELECT INTO STRICT old_count stack_size FROM items WHERE id = new_item_id FOR SHARE;
		new_count = old_count + in_increment;
		IF new_count > in_max_count THEN new_count = in_max_count; END IF;
		IF new_count != old_count THEN
			delta_count = new_count - old_count;
			SELECT INTO new_item_id merge_or_move_inventory_item(in_item_id, exchange_inventory_id, new_order_id, delta_count);
			RETURN delta_count;
		END IF;

		RETURN 0;
	END IF;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58257
f
dune_get_account_id_by_user
in_user text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.dune_get_account_id_by_user(in_user text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	account_id BIGINT;
BEGIN
	SELECT INTO account_id id FROM accounts WHERE "user"=in_user;
	RETURN account_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58258
f
edit_guild_description
in_guild_id bigint, in_guild_desc text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.edit_guild_description(in_guild_id bigint, in_guild_desc text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_guild_description TEXT;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	-- check if guild exists
	SELECT guild_description INTO out_guild_description FROM guilds WHERE guild_id = in_guild_id;
	IF NOT FOUND THEN
    	RAISE EXCEPTION 'Trying to add invite to non existing guild %.', in_guild_id;
	END IF;

	UPDATE guilds SET guild_description = in_guild_desc WHERE guilds.guild_id = in_guild_id;

	PERFORM pg_notify('guild_notify_channel', format('edit_guild_description#{"GuildId" : %s}', in_guild_id));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58259
f
encrypt_user_data
in_data text
bytea

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.encrypt_user_data(in_data text)
 RETURNS bytea
 LANGUAGE sql
 IMMUTABLE
AS $function$select convert_to(in_data, 'utf8')$function$

@@@ENDRTN@@@
@@@RTN@@@58260
f
fetch_resourcefield_state
in_map text, in_dimension_index integer, in_field_kind_id smallint
TABLE(field_id bigint, spawn_time double precision, value_remaining bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.fetch_resourcefield_state(in_map text, in_dimension_index integer, in_field_kind_id smallint)
 RETURNS TABLE(field_id bigint, spawn_time double precision, value_remaining bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY 
	SELECT resourcefield_state.field_id, resourcefield_state.spawn_time, resourcefield_state.value_remaining 
	FROM resourcefield_state 
	WHERE map = in_map AND dimension_index = in_dimension_index AND field_kind_id = in_field_kind_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58261
f
fetch_server_spice_field_manifest
in_server_id text
TABLE(spicefield_type_id integer, inactive_fields_of_type integer, requested_spawned_of_type integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.fetch_server_spice_field_manifest(in_server_id text)
 RETURNS TABLE(spicefield_type_id integer, inactive_fields_of_type integer, requested_spawned_of_type integer)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT t.spicefield_type_id, t.inactive_fields_of_type, t.requested_spawned_of_type
    FROM spicefield_server_availability as t
    WHERE t.server_id = in_server_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58262
f
fetch_spicefie_id_types_with_global_info
in_map_name text, in_dimension_index integer
TABLE(spicefield_type_id integer, max_globally_active integer, max_globally_primed integer, current_globally_active integer, current_globally_primed integer, is_spawning_active boolean, field_type text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.fetch_spicefie_id_types_with_global_info(in_map_name text, in_dimension_index integer)
 RETURNS TABLE(spicefield_type_id integer, max_globally_active integer, max_globally_primed integer, current_globally_active integer, current_globally_primed integer, is_spawning_active boolean, field_type text)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT t.spicefield_type_id, t.max_globally_active, t.max_globally_primed, t.current_globally_active, t.current_globally_primed, t.is_spawning_active, t.field_type
    FROM spicefield_types as t
    WHERE t.map_name = in_map_name AND t.dimension_index = in_dimension_index;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58263
f
find_actor_by_id
in_id bigint
dune.actorspawninfo

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.find_actor_by_id(in_id bigint)
 RETURNS dune.actorspawninfo
 LANGUAGE plpgsql
AS $function$
begin
	return (select (id, class, transform, partition_id, dimension_index)::ActorSpawnInfo
		FROM actors WHERE actors.id = in_id
		LIMIT 1);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58264
f
fix_broken_harkonnen_players_due_to_fooled_thufir

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.fix_broken_harkonnen_players_due_to_fooled_thufir()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN

    -- Dropping tables due to them being created in temp_backup_tables.sql
    drop table if exists da_6358_pre_broken_players;
    drop table if exists da_6358_broken_players_12400;
    drop table if exists da_6358_broken_players_1300;

---Fix players that are in a broken state but haven't been blocked yet
--Tag combo to check for:
--DialogueFlags.Factions.Hark_ThufirBetrayedComplete
--DialogueFlags.Faction.FooledThufir
--PlayerIsFactionTier Harkonnen 5
--TO FIX:
--Remove DialogueFlags.Faction.FooledThufir
    create table if not exists da_6358_pre_broken_players as
    select pt.account_id from player_tags pt
    group by pt.account_id
    HAVING array_agg(DISTINCT tag) @> ARRAY['DialogueFlags.Factions.Hark_ThufirBetrayedComplete', 'DialogueFlags.Faction.FooledThufir', 'Faction.Harkonnen.Tier5'];

    delete from player_tags pt using da_6358_pre_broken_players where pt.account_id = da_6358_pre_broken_players.account_id and pt.tag = 'DialogueFlags.Faction.FooledThufir';

---Fix players that are in a broken state since 1.2.40
---Tag combo to check for:
---DialogueFlags.Factions.Hark_ThufirBetrayedComplete
---DialogueFlags.Faction.FooledThufir
---Contract.Tracking.FactionStory.R4C6Completed
---PlayerIsFactionTier Harkonnen 4
---TO FIX:
---Remove DialogueFlags.Faction.FooledThufir
---Promote player to Harkonnen 5

    create table if not exists da_6358_broken_players_12400 as
    with broken_accounts as (
        select pt.account_id
        from player_tags pt
        group by pt.account_id
        having array_agg(distinct pt.tag) @> array[
            'DialogueFlags.Factions.Hark_ThufirBetrayedComplete',
            'DialogueFlags.Faction.FooledThufir',
            'Contract.Tracking.FactionStory.R4C6Completed',
            'Faction.Harkonnen.Tier4'
            ]
           and not array_agg(distinct pt.tag) && array['Faction.Harkonnen.Tier5']
    ),
    one_state_row_per_account as (
        -- If player_state can theoretically have duplicates per account_id,
        -- pick one deterministically.
        select distinct on (ps.account_id)
             ps.account_id,
             ps.player_controller_id
         from player_state ps
                  join broken_accounts ba on ba.account_id = ps.account_id
         order by ps.account_id, ps.player_controller_id desc
    )
    select
        s.account_id,
        s.player_controller_id,
        a.properties as backup_properties
    from one_state_row_per_account s
             join actors a on a.id = s.player_controller_id;

    INSERT INTO player_tags (account_id, tag)
    SELECT account_id, 'Faction.Harkonnen.Tier5' as tag
    FROM da_6358_broken_players_12400
    ON CONFLICT (account_id, tag) DO NOTHING;

    delete from player_tags pt using da_6358_broken_players_12400 where pt.account_id = da_6358_broken_players_12400.account_id and pt.tag = 'DialogueFlags.Faction.FooledThufir';
    UPDATE actors
    SET properties = jsonb_set(
        properties,
        '{FactionPlayerComponent,m_FactionDataArray}',
        (
            SELECT coalesce(
                       jsonb_agg(
                           CASE
                               WHEN elem->'Faction'->>'Name' = 'Harkonnen'
                                   THEN jsonb_set(elem, '{ReputationAmount}', '2000'::jsonb)
                               ELSE elem
                               END
                       ),
                       '[]'::jsonb
                   )
            FROM jsonb_array_elements(properties->'FactionPlayerComponent'->'m_FactionDataArray') AS t(elem)
        )
    )
    FROM da_6358_broken_players_12400 b12400
    WHERE b12400.player_controller_id = actors.id;

---Fix players that are in a broken state in live aka 1.3.0
---Tag combo to check for:
---DialogueFlags.Factions.Hark_ThufirBetrayedComplete
---Contract.Tracking.FactionStory.R4C6Completed
---PlayerIsFactionTier Harkonnen 4
---NOT DialogueFlags.Faction.FooledThufir
    create table if not exists da_6358_broken_players_1300 as
    with broken_accounts as (
        select pt.account_id
        from player_tags pt
        group by pt.account_id
        HAVING array_agg(DISTINCT tag) @> ARRAY ['DialogueFlags.Factions.Hark_ThufirBetrayedComplete', 'Contract.Tracking.FactionStory.R4C6Completed', 'Faction.Harkonnen.Tier4']
           and not array_agg(distinct tag) && array['DialogueFlags.Faction.FooledThufir', 'Faction.Harkonnen.Tier5'] -- Since Tier4 players can also have Tier5 we need to exclude that
    ),
         one_state_row_per_account as (
             -- If player_state can theoretically have duplicates per account_id,
             -- pick one deterministically.
             select distinct on (ps.account_id)
                 ps.account_id,
                 ps.player_controller_id
             from player_state ps
                      join broken_accounts ba on ba.account_id = ps.account_id
             order by ps.account_id, ps.player_controller_id desc
         )
    select
        s.account_id,
        s.player_controller_id,
        a.properties as backup_properties
    from one_state_row_per_account s
             join actors a on a.id = s.player_controller_id;

    INSERT INTO player_tags (account_id, tag)
    SELECT account_id, 'Faction.Harkonnen.Tier5' as tag
    FROM da_6358_broken_players_1300
    ON CONFLICT (account_id, tag) DO NOTHING;

    UPDATE actors
    SET properties = jsonb_set(
        properties,
        '{FactionPlayerComponent,m_FactionDataArray}',
        (
            SELECT coalesce(
                       jsonb_agg(
                           CASE
                               WHEN elem->'Faction'->>'Name' = 'Harkonnen'
                                   THEN jsonb_set(elem, '{ReputationAmount}', '2000'::jsonb)
                               ELSE elem
                               END
                       ),
                       '[]'::jsonb
                   )
            FROM jsonb_array_elements(properties->'FactionPlayerComponent'->'m_FactionDataArray') AS t(elem)
        )
                     )
    FROM da_6358_broken_players_1300 b1300
    WHERE b1300.player_controller_id = actors.id;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58265
f
flag_player_as_cheater
in_account_id bigint, in_cheat_type dune.cheat_type_enum
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.flag_player_as_cheater(in_account_id bigint, in_cheat_type dune.cheat_type_enum)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_FLS_id TEXT;
BEGIN
    SELECT acc."user"
        INTO v_FLS_id
        FROM accounts acc
        WHERE acc.id = in_account_id
        LIMIT 1;

    PERFORM log_cheating(v_FLS_id, in_cheat_type);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58266
f
gather_ownerless_actors_on_server
in_server_info dune.serverinfo
SETOF dune.actorspawninfo

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.gather_ownerless_actors_on_server(in_server_info dune.serverinfo)
 RETURNS SETOF dune.actorspawninfo
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
		SELECT a.id, a.class as class_name, a.transform, a.partition_id, a.dimension_index FROM actors as a
		WHERE a.owner_account_id is null AND server_info_match(a, in_server_info);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58267
f
gather_player_linked_actors
in_player_pawn_id bigint
SETOF dune.actorspawninfo

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.gather_player_linked_actors(in_player_pawn_id bigint)
 RETURNS SETOF dune.actorspawninfo
 LANGUAGE plpgsql
AS $function$
begin
	return query
		select actors.id, actors.class as class_name, actors.transform, actors.partition_id, actors.dimension_index
		from actors
		left join actor_state on actor_state.actor_id = actors.id
		where actors.id in (select id from get_traveling_non_player_actor_ids(in_player_pawn_id)) and actor_state.state = 'Travel' and actors.owner_account_id is null
		order by actors.id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58268
f
gather_removed_accounts_that_left_orphaned_actors_on_server
in_server_info dune.serverinfo
TABLE(account_id bigint, removal_reason text, actors_left dune.orphanedplayeractorinfo[])

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.gather_removed_accounts_that_left_orphaned_actors_on_server(in_server_info dune.serverinfo)
 RETURNS TABLE(account_id bigint, removal_reason text, actors_left dune.orphanedplayeractorinfo[])
 LANGUAGE plpgsql
AS $function$
BEGIN
    return query with
        orphaned_actors_per_account as (
            SELECT
                a.owner_account_id as account_id,
                array_agg((a.id, a.class)::OrphanedPlayerActorInfo) as actors_left
            FROM actors as a
            WHERE
                -- not is null instead of is not null to match the index expression
                not a.owner_account_id is null
                AND NOT EXISTS(select 1 from accounts where id=owner_account_id)
                AND server_info_match(a, in_server_info)
            GROUP BY a.owner_account_id
        )
        select orphans.account_id, log.reason, orphans.actors_left
            from orphaned_actors_per_account as orphans left join account_removal_log as log using (account_id);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58269
f
get_account_actor_ids
in_account_id bigint
dune.playeractorids

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_account_actor_ids(in_account_id bigint)
 RETURNS dune.playeractorids
 LANGUAGE plpgsql
AS $function$
BEGIN
    return (
        select (player_controller_id, player_state_id, player_pawn_id)::PlayerActorIds from player_state
        where account_id = in_account_id
        limit 1
    );
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58270
f
get_active_servers_for_gateway

TABLE(server_id text, map text, partition_id bigint, dimension_index integer, game_addr inet, game_port integer, revision integer)
Used by the gateway service to monitor for active servers.
@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_active_servers_for_gateway()
 RETURNS TABLE(server_id text, map text, partition_id bigint, dimension_index integer, game_addr inet, game_port integer, revision integer)
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
	-- If we have no partitions, assume dimension 0
	return query select fs.server_id, fs.map, wp.partition_id, coalesce(wp.dimension_index, 0), fs.game_addr, fs.game_port, fs.revision from active_server_ids as asi left join world_partition as wp on asi.server_id = wp.server_id join farm_state as fs on fs.server_id = asi.server_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58271
f
get_actor_server_info
in_id bigint
dune.serverinfo

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_actor_server_info(in_id bigint)
 RETURNS dune.serverinfo
 LANGUAGE plpgsql
AS $function$
begin
	return (select (map, partition_id, dimension_index)::ServerInfo from actors where id=in_id limit 1);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58272
f
get_actors_location_data_with_permission
in_actor_ids bigint[]
SETOF dune.actorpermissionlocationdata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_actors_location_data_with_permission(in_actor_ids bigint[])
 RETURNS SETOF dune.actorpermissionlocationdata
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	SELECT actors.id, actors.partition_id, actors.map, actors.dimension_index, actors.transform
	FROM actors
	WHERE actors.id = ANY(in_actor_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58273
f
get_all_demo_players

TABLE(fls_ids text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_demo_players()
 RETURNS TABLE(fls_ids text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT fls_id
	FROM demo_users
    WHERE demo_playtime_seconds IS NOT NULL;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58274
f
get_all_faction_members

TABLE(player_id bigint, fls_id text, faction_id smallint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_faction_members()
 RETURNS TABLE(player_id bigint, fls_id text, faction_id smallint)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	SELECT ps.player_controller_id as player_id, acc.user as fls_id, f.faction_id
	FROM accounts acc
	LEFT JOIN player_state ps ON acc.id = ps.account_id
	RIGHT JOIN player_faction f ON ps.player_controller_id = f.actor_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58275
f
get_all_guild_members

TABLE(player_id bigint, fls_id text, guild_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_guild_members()
 RETURNS TABLE(player_id bigint, fls_id text, guild_id bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	SELECT gm.player_id, acc.user, gm.guild_id
	FROM accounts acc
	LEFT JOIN player_state ps ON acc.id = ps.account_id
	RIGHT JOIN guild_members gm ON ps.player_controller_id = gm.player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58276
f
get_all_online_or_recently_disconnected_player_online_state

SETOF dune.playeronlinestateentry

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_online_or_recently_disconnected_player_online_state()
 RETURNS SETOF dune.playeronlinestateentry
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT actors.id, player_state.character_name, (actors.map, actors.partition_id, actors.dimension_index)::ServerInfo, (player_state.last_avatar_activity AT TIME ZONE 'UTC')::TIMESTAMP, player_state.online_status
	FROM actors
	JOIN player_state ON player_state.player_controller_id = actors.id
	WHERE (player_state.online_status = 'Online' 
		or player_state.last_avatar_activity > NOW() - INTERVAL '1 minutes' );
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58277
f
get_all_parties

TABLE(party_id bigint, player_id bigint, player_name text, party_leader_id bigint, platform_session_id text, platform_name text, platform_players_count integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_parties()
 RETURNS TABLE(party_id bigint, player_id bigint, player_name text, party_leader_id bigint, platform_session_id text, platform_name text, platform_players_count integer)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT
        parties.party_id,
        party_members.player_id,
        player_state.character_name,
        parties.party_leader_id,
        platform_parties_mapping.platform_session_id,
        platform_parties_mapping.platform_name,
        platform_parties_mapping.num_of_players
    FROM party_members
    JOIN parties ON party_members.party_id = parties.party_id
    JOIN player_state ON player_state.player_controller_id = party_members.player_id
    LEFT JOIN platform_parties_mapping ON platform_parties_mapping.dune_party_id = parties.party_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58278
f
get_all_party_invites

TABLE(invite_id bigint, party_id bigint, sender_player_id bigint, sender_name text, player_id bigint, player_name text, invite_sent_timespan bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_party_invites()
 RETURNS TABLE(invite_id bigint, party_id bigint, sender_player_id bigint, sender_name text, player_id bigint, player_name text, invite_sent_timespan bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT party_invites.invite_id, party_invites.party_id, party_invites.sender_player_id, sender_player_state.character_name, party_invites.player_id, player_state.character_name, party_invites.invite_sent_timespan
	FROM party_invites
	JOIN player_state ON player_state.player_controller_id = party_invites.player_id
	JOIN player_state AS sender_player_state ON sender_player_state.player_controller_id = party_invites.sender_player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58279
f
get_all_party_members

TABLE(player_id bigint, fls_id text, party_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_party_members()
 RETURNS TABLE(player_id bigint, fls_id text, party_id bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	SELECT pm.player_id, acc.user, pm.party_id
	FROM party_members pm
	LEFT JOIN player_state ps ON ps.player_controller_id = pm.player_id
	LEFT JOIN accounts acc ON acc.id = ps.account_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58280
f
get_all_player_character_home_dimensions

TABLE(fls_id text, home_dimension integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_player_character_home_dimensions()
 RETURNS TABLE(fls_id text, home_dimension integer)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT accounts.user as fls_id, player_state.home_dimension_index as home_dimension
	FROM accounts
	LEFT JOIN player_state on player_state.account_id = accounts.id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58281
f
get_all_player_in_guild_online_state
in_guild_id bigint
SETOF dune.playeronlinestateentry

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_player_in_guild_online_state(in_guild_id bigint)
 RETURNS SETOF dune.playeronlinestateentry
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT actors.id, player_state.character_name, (actors.map, actors.partition_id, actors.dimension_index)::ServerInfo, (player_state.last_avatar_activity AT TIME ZONE 'UTC')::TIMESTAMP, player_state.online_status
	FROM actors
	JOIN player_state ON player_state.player_controller_id = actors.id
    JOIN guild_members ON guild_members.player_id = actors.id
	WHERE guild_members.guild_id = in_guild_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58282
f
get_all_player_travel_states

TABLE(fls_id text, login_target_dimension_index integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_player_travel_states()
 RETURNS TABLE(fls_id text, login_target_dimension_index integer)
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
    RETURN query SELECT pts.fls_id, pts.login_target_dimension_index FROM player_travel_state AS pts;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58283
f
get_all_tutorial_entries
in_player_id bigint
TABLE(tutorial_id smallint, tutorial_state smallint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_tutorial_entries(in_player_id bigint)
 RETURNS TABLE(tutorial_id smallint, tutorial_state smallint)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY SELECT tutorial_per_player.tutorial_id, tutorial_per_player.tutorial_state FROM tutorial_per_player WHERE tutorial_per_player.player_id = in_player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58284
f
get_all_unresolved_character_imports

TABLE(flsid text, importstate dune.transferimportstate, lastupdatetime timestamp with time zone)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_all_unresolved_character_imports()
 RETURNS TABLE(flsid text, importstate dune.transferimportstate, lastupdatetime timestamp with time zone)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT ti.fls_id, ti.transfer_state, ti.last_update
	FROM character_transfer_imports ti;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58285
f
get_applied_patches

SETOF text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_applied_patches()
 RETURNS SETOF text
 LANGUAGE sql
AS $function$
	select "name" from applied_patches order by "date";
$function$

@@@ENDRTN@@@
@@@RTN@@@58286
f
get_battlegroup_close_date

timestamp without time zone

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_battlegroup_close_date()
 RETURNS timestamp without time zone
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN (SELECT farm_variables.battlegroup_close_date from farm_variables);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58287
f
get_best_dungeon_completion
in_dungeon_id text
TABLE(out_difficulty integer, out_duration_ms integer, out_players_names text[])

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_best_dungeon_completion(in_dungeon_id text)
 RETURNS TABLE(out_difficulty integer, out_duration_ms integer, out_players_names text[])
 LANGUAGE plpgsql
AS $function$
begin
	RETURN QUERY SELECT
		d.difficulty,
		d.duration_ms,
		COALESCE((SELECT array_agg(COALESCE(ps.character_name, ''))
			FROM player_state as ps
			FULL JOIN dungeon_completion_players as dcp ON dcp.player_id = ps.player_controller_id
			WHERE dcp.completion_id = d.completion_id), ARRAY[]::TEXT[])
		FROM dungeon_completion as d
		WHERE d.dungeon_id = in_dungeon_id
		ORDER BY d.difficulty DESC, d.players_num ASC, d.duration_ms ASC, d.completion_id ASC
		LIMIT 1;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58288
f
get_best_dungeons_completions_for_player
in_player_id bigint
TABLE(out_dungeon_id text, out_difficulty integer, out_duration_ms integer, out_players_num smallint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_best_dungeons_completions_for_player(in_player_id bigint)
 RETURNS TABLE(out_dungeon_id text, out_difficulty integer, out_duration_ms integer, out_players_num smallint)
 LANGUAGE plpgsql
AS $function$
begin
	RETURN QUERY SELECT DISTINCT ON (d.dungeon_id) d.dungeon_id, d.difficulty, d.duration_ms, d.players_num
		FROM dungeon_completion as d
			INNER JOIN dungeon_completion_players as p ON p.completion_id = d.completion_id
		WHERE p.player_id = in_player_id
		ORDER BY d.dungeon_id, d.difficulty DESC, d.players_num ASC, d.duration_ms ASC, d.completion_id ASC;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58289
f
get_building_blueprint_copy_data
in_building_blueprint_id bigint
dune.buildingblueprintgetcopydata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_building_blueprint_copy_data(in_building_blueprint_id bigint)
 RETURNS dune.buildingblueprintgetcopydata
 LANGUAGE plpgsql
AS $function$
DECLARE
    buildings_array BuildingBlueprintItem[];
    placeables_array BuildingBlueprintPlaceableItem[];
    pentashields_array BuildingBlueprintPentashieldItem[];
BEGIN
    -- All Building Pieces
    SELECT array_agg((instance_id, building_type, transform, provides_stability, health, hologram)::BuildingBlueprintItem)
		into buildings_array
		FROM building_blueprint_instances
		WHERE building_blueprint_id = in_building_blueprint_id;

    -- All Placeables
    SELECT array_agg((placeable_id, building_type, transform, hologram)::BuildingBlueprintPlaceableItem)
		into placeables_array
		FROM building_blueprint_placeables
		WHERE building_blueprint_id = in_building_blueprint_id;

    -- Pentashields
    SELECT array_agg((placeable_id, scale)::BuildingBlueprintPentashieldItem)
		into pentashields_array
		FROM building_blueprint_pentashields
		WHERE building_blueprint_id = in_building_blueprint_id;

    return ROW(buildings_array, placeables_array, pentashields_array)::BuildingBlueprintGetCopyData;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58290
f
get_building_favorites
in_account_id bigint
TABLE(building_types text[])

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_building_favorites(in_account_id bigint)
 RETURNS TABLE(building_types text[])
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT building_favorites.building_types FROM building_favorites WHERE account_id = in_account_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58291
f
get_building_id
in_actor_id bigint, in_class text
dune.buildinggetidcomposite

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_building_id(in_actor_id bigint, in_class text)
 RETURNS dune.buildinggetidcomposite
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF not exists(select 1 from buildings where "id" = in_actor_id) THEN
		if in_actor_id is null or in_actor_id = 0 then
			in_actor_id := (SELECT assign_actor_id(in_class));
		end if;

		INSERT INTO buildings("id") VALUES(in_actor_id);
	END IF;

    return ROW(in_actor_id)::BuildingGetIdComposite;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58292
f
get_character_import_state
in_fls_id text
dune.transferimportstate

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_character_import_state(in_fls_id text)
 RETURNS dune.transferimportstate
 LANGUAGE plpgsql
AS $function$
DECLARE
	v_state TransferImportState;
BEGIN
	SELECT transfer_state INTO v_state FROM character_transfer_imports WHERE fls_id = in_fls_id;
	RETURN v_state;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58293
f
get_character_transfer_related_items
in_fls_id text
jsonb

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_character_transfer_related_items(in_fls_id text)
 RETURNS jsonb
 LANGUAGE plpgsql
AS $function$
BEGIN

RETURN (
	WITH acct AS (
		SELECT id
		FROM accounts
		WHERE "user" = in_FLS_ID
		LIMIT 1
	)
	SELECT jsonb_build_object(
		'items', COALESCE((
			SELECT jsonb_agg(jsonb_build_object('name', sub.template_id, 'amount', sub.amount))
			FROM (
			SELECT i.template_id, SUM(i.stack_size) AS amount
			FROM inventories inv
			JOIN items i ON i.inventory_id = inv.id
			JOIN player_state ps ON inv.actor_id = ps.player_pawn_id
			WHERE ps.account_id = acct.id AND (
				(inv.inventory_type = 0 AND i.template_id = 'SolarisCoin') OR
				(i.template_id IN ('BaseBackupTool', 'VehicleBackupTool'))
				)
			GROUP BY i.template_id
			) sub
		), '[]'::jsonb),
		'coin_balance', COALESCE((
            SELECT vc.balance from player_virtual_currency_balances vc
            JOIN player_state ps ON ps.account_id = acct.id
            WHERE vc.currency_id = get_solaris_id()
            AND vc.player_controller_id = ps.player_controller_id
			LIMIT 1
		), 0)
	)
	FROM acct
);

END
$function$

@@@ENDRTN@@@
@@@RTN@@@58294
f
get_consumed_lore_pickups
in_actor_id bigint, in_use_temporary boolean
SETOF bit

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_consumed_lore_pickups(in_actor_id bigint, in_use_temporary boolean)
 RETURNS SETOF bit
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF in_use_temporary THEN
		RETURN query
			SELECT consumed_bit_array
			FROM consumed_temporary_per_player_lore
			WHERE actor_id = in_actor_id;
	ELSE
		RETURN query
			SELECT consumed_bit_array
			FROM consumed_per_player_lore
			WHERE actor_id = in_actor_id;
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58295
f
get_controller_id_from_platform_id
in_platform_id text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_controller_id_from_platform_id(in_platform_id text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
    out_controller_id BIGINT;
BEGIN
    SELECT ps.player_controller_id
    INTO out_controller_id
    FROM accounts acc LEFT JOIN player_state ps ON acc.id=ps.account_id
    WHERE acc.platform_id = in_platform_id
    LIMIT 1;
    RETURN out_controller_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58296
f
get_dune_exchange_accesspoint_id
in_exchange_id bigint, in_name text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_dune_exchange_accesspoint_id(in_exchange_id bigint, in_name text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	new_ap_id BIGINT;
	ap_id BIGINT;
BEGIN
	INSERT INTO dune_exchange_accesspoints(exchange_id, name) VALUES(in_exchange_id, in_name) ON CONFLICT DO NOTHING RETURNING id INTO new_ap_id;
	SELECT INTO ap_id COALESCE(new_ap_id, id) FROM dune_exchange_accesspoints WHERE exchange_id = in_exchange_id AND name = in_name;

	return ap_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58297
f
get_dune_exchange_data
in_exchange_id bigint, in_controller_id bigint
dune.loadexchangedataresult

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_dune_exchange_data(in_exchange_id bigint, in_controller_id bigint)
 RETURNS dune.loadexchangedataresult
 LANGUAGE plpgsql
AS $function$
DECLARE
	result LoadExchangeDataResult;
BEGIN
	SELECT INTO result.exchange_name exchange_name FROM dune_exchanges WHERE id=in_exchange_id;
	SELECT INTO result.used_order_slots get_dune_exchange_used_order_slots(in_controller_id);

	RETURN result;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58298
f
get_dune_exchange_id
in_name text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_dune_exchange_id(in_name text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	new_exchange_id BIGINT;
	exchange_id BIGINT;
BEGIN
	INSERT INTO dune_exchanges(exchange_name, inventory_id) VALUES(in_name, NULL) ON CONFLICT DO NOTHING RETURNING id INTO new_exchange_id;
	SELECT INTO exchange_id COALESCE(new_exchange_id, id) FROM dune_exchanges WHERE exchange_name = in_name;
	RETURN exchange_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58299
f
get_dune_exchange_used_order_slots
in_controller_id bigint
integer

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_dune_exchange_used_order_slots(in_controller_id bigint)
 RETURNS integer
 LANGUAGE plpgsql
AS $function$
DECLARE
	result INT;
BEGIN
	SELECT INTO result COUNT(*) FROM dune_exchange_orders where owner_id=in_controller_id AND item_id IS NOT NULL;

	RETURN result;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58300
f
get_exchange_inventory_id
in_exchange_id bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_exchange_inventory_id(in_exchange_id bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	inv_id BIGINT;
BEGIN
	SELECT INTO inv_id id FROM inventories WHERE "exchange_id" = in_exchange_id;
	IF inv_id IS NULL THEN
		INSERT INTO inventories("id", exchange_id) VALUES(DEFAULT, in_exchange_id) RETURNING id INTO inv_id;
	END IF;
	RETURN inv_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58301
f
get_exchange_orders_by_mask
in_mask integer, in_depth smallint
SETOF bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_exchange_orders_by_mask(in_mask integer, in_depth smallint)
 RETURNS SETOF bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	check_mask INT;
	check_shift INT;
BEGIN
	check_shift := (4 - in_depth) * 8;
	check_mask := (in_mask >> check_shift);
	RETURN query SELECT id FROM dune_exchange_orders WHERE category_depth >= in_depth AND (category_mask >> check_shift) = check_mask FOR SHARE;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58302
f
get_exchange_sell_orders
in_id bigint, in_exchange_id bigint, in_min_item_price bigint, in_max_item_price bigint, in_template_id text, in_mask integer, in_depth smallint
TABLE(id bigint, revision bigint, expiration_time bigint, access_point_id bigint, ap_name text, owner_id bigint, template_id text, stack_size bigint, initial_stack_size bigint, item_price bigint, quality_level bigint, durability_cur real, durability_max real, dynamic_stats jsonb)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_exchange_sell_orders(in_id bigint, in_exchange_id bigint, in_min_item_price bigint, in_max_item_price bigint, in_template_id text, in_mask integer, in_depth smallint)
 RETURNS TABLE(id bigint, revision bigint, expiration_time bigint, access_point_id bigint, ap_name text, owner_id bigint, template_id text, stack_size bigint, initial_stack_size bigint, item_price bigint, quality_level bigint, durability_cur real, durability_max real, dynamic_stats jsonb)
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
	RETURN query
		SELECT ord.id, ord.revision, ord.expiration_time, ord.access_point_id, ap.name, ord.owner_id, item.template_id, item.stack_size, sord.initial_stack_size, ord.item_price, ord.quality_level, ord.durability_cur, ord.durability_max, item.stats
		FROM dune_exchange_orders ord
		JOIN dune_exchange_sell_orders sord ON (ord.id = sord.order_id)
		JOIN items item ON (ord.item_id = item.id)
		JOIN dune_exchange_accesspoints ap ON (ord.access_point_id = ap.id)
		WHERE 	(in_id IS NOT NULL AND ord.id = in_id) OR
				(in_id IS NULL AND ord.exchange_id = in_exchange_id AND ord.item_price >= in_min_item_price AND (in_max_item_price <= 0 OR ord.item_price <= in_max_item_price) AND
					((in_template_id IS NOT NULL AND ord.template_id = in_template_id) OR
					(in_template_id IS NULL AND ord.id IN (SELECT * FROM get_exchange_orders_by_mask(in_mask, in_depth))))
				)
		FOR SHARE;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58303
f
get_exchange_sell_orders_by_item_type
in_exchange_id bigint, in_template_ids text[]
TABLE(id bigint, revision bigint, expiration_time bigint, access_point_id bigint, ap_name text, owner_id bigint, template_id text, stack_size bigint, initial_stack_size bigint, item_price bigint, quality_level bigint, durability_cur real, durability_max real, dynamic_stats jsonb)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_exchange_sell_orders_by_item_type(in_exchange_id bigint, in_template_ids text[])
 RETURNS TABLE(id bigint, revision bigint, expiration_time bigint, access_point_id bigint, ap_name text, owner_id bigint, template_id text, stack_size bigint, initial_stack_size bigint, item_price bigint, quality_level bigint, durability_cur real, durability_max real, dynamic_stats jsonb)
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
	RETURN query
		SELECT ord.id, ord.revision, ord.expiration_time, ord.access_point_id, ap.name, ord.owner_id, item.template_id, item.stack_size, sord.initial_stack_size, ord.item_price, ord.quality_level, ord.durability_cur, ord.durability_max, item.stats
		FROM dune_exchange_orders ord
		JOIN dune_exchange_sell_orders sord ON (ord.id = sord.order_id)
		JOIN items item ON (ord.item_id = item.id)
		JOIN dune_exchange_accesspoints ap ON (ord.access_point_id = ap.id)
		WHERE ord.exchange_id = in_exchange_id AND ord.template_id = ANY(in_template_ids)
		FOR SHARE;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58304
f
get_exchange_sell_orders_by_owner
in_exchange_id bigint, in_owner_id bigint
TABLE(id bigint, revision bigint, expiration_time bigint, access_point_id bigint, ap_name text, owner_id bigint, template_id text, stack_size bigint, initial_stack_size bigint, item_price bigint, quality_level bigint, durability_cur real, durability_max real, dynamic_stats jsonb)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_exchange_sell_orders_by_owner(in_exchange_id bigint, in_owner_id bigint)
 RETURNS TABLE(id bigint, revision bigint, expiration_time bigint, access_point_id bigint, ap_name text, owner_id bigint, template_id text, stack_size bigint, initial_stack_size bigint, item_price bigint, quality_level bigint, durability_cur real, durability_max real, dynamic_stats jsonb)
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
	RETURN query
		SELECT ord.id, ord.revision, ord.expiration_time, ord.access_point_id, ap.name, ord.owner_id, item.template_id, item.stack_size, sord.initial_stack_size, ord.item_price, ord.quality_level, ord.durability_cur, ord.durability_max, item.stats
		FROM dune_exchange_orders ord
		JOIN dune_exchange_sell_orders sord ON (ord.id = sord.order_id)
		JOIN items item ON (ord.item_id = item.id)
		JOIN dune_exchange_accesspoints ap ON (ord.access_point_id = ap.id)
		WHERE ord.owner_id = in_owner_id
		FOR SHARE;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58305
f
get_farm_state

TABLE(server_id text, farm_id text, outgoing_s2s_connections integer, incoming_s2s_connections integer, connected_players integer, igw_addr inet, igw_port integer, game_addr inet, game_port integer, ready boolean, alive boolean, map text, revision integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_farm_state()
 RETURNS TABLE(server_id text, farm_id text, outgoing_s2s_connections integer, incoming_s2s_connections integer, connected_players integer, igw_addr inet, igw_port integer, game_addr inet, game_port integer, ready boolean, alive boolean, map text, revision integer)
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
	RETURN QUERY SELECT fs.server_id, fs.farm_id, fs.outgoing_s2s_connections, fs.incoming_s2s_connections, fs.connected_players, fs.igw_addr, fs.igw_port, fs.game_addr, fs.game_port, fs.ready, fs.alive, fs.map, fs.revision FROM farm_state as fs
		JOIN active_server_ids USING(server_id);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58306
f
get_friends_search
in_player_name text, in_max_players_count integer
TABLE(player_id bigint, character_name text, funcom_id text, platform_id text, platform_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_friends_search(in_player_name text, in_max_players_count integer)
 RETURNS TABLE(player_id bigint, character_name text, funcom_id text, platform_id text, platform_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
		RETURN QUERY SELECT player_state.player_controller_id, player_state.character_name, accounts.funcom_id, accounts.platform_id, accounts.platform_name
		FROM player_state
		JOIN accounts ON player_state.account_id = accounts.id
		WHERE player_state.character_name ILIKE '%' || in_player_name || '%'
		ORDER BY ext.SIMILARITY(player_state.character_name, in_player_name) DESC
		LIMIT in_max_players_count;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58307
f
get_guild_data
in_guild_id bigint
TABLE(guild_name text, guild_faction_id smallint, guild_description text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_guild_data(in_guild_id bigint)
 RETURNS TABLE(guild_name text, guild_faction_id smallint, guild_description text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	SELECT guilds.guild_name, guilds.guild_faction, guilds.guild_description
	FROM guilds
	WHERE guild_id = in_guild_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58308
f
get_guild_data_for_player
in_player_id bigint
TABLE(guild_id bigint, guild_factions_id smallint, guild_name text, guild_description text, player_id bigint, role_id smallint, player_faction_id smallint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_guild_data_for_player(in_player_id bigint)
 RETURNS TABLE(guild_id bigint, guild_factions_id smallint, guild_name text, guild_description text, player_id bigint, role_id smallint, player_faction_id smallint)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	SELECT guilds.guild_id, guilds.guild_faction, guilds.guild_name, guilds.guild_description, guild_members.player_id, guild_members.role_id, player_faction.faction_id
	FROM guilds JOIN guild_members on (guilds.guild_id = guild_members.guild_id)
	LEFT JOIN player_faction on player_faction.actor_id = guild_members.player_id
	WHERE guild_members.player_id = in_player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58309
f
get_guild_for_player
in_player_id bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_guild_for_player(in_player_id bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	found_guild_id integer;
BEGIN
	SELECT guild_id FROM guild_members WHERE player_id = in_player_id INTO found_guild_id;
	IF NOT FOUND THEN
    	RETURN 0;
	END IF;
	RETURN found_guild_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58310
f
get_guild_invites
in_guild_id bigint
TABLE(invite_id bigint, player_id bigint, sender_player_id bigint, invite_sent_timespan bigint, character_name text, sender_character_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_guild_invites(in_guild_id bigint)
 RETURNS TABLE(invite_id bigint, player_id bigint, sender_player_id bigint, invite_sent_timespan bigint, character_name text, sender_character_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT guild_invites.invite_id, guild_invites.player_id, guild_invites.sender_player_id, guild_invites.invite_sent_timespan, player_state.character_name, sender_player_state.character_name AS sender_character_name
	FROM guild_invites
	JOIN player_state ON player_state.player_controller_id = guild_invites.player_id
	JOIN player_state AS sender_player_state ON sender_player_state.player_controller_id =  guild_invites.sender_player_id
	WHERE guild_id = in_guild_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58311
f
get_guild_members
in_guild_id bigint
TABLE(player_id bigint, role_id smallint, player_faction_id smallint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_guild_members(in_guild_id bigint)
 RETURNS TABLE(player_id bigint, role_id smallint, player_faction_id smallint)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	SELECT guild_members.player_id, guild_members.role_id, player_faction.faction_id
	FROM guild_members
	LEFT JOIN player_faction ON player_faction.actor_id = guild_members.player_id
	WHERE guild_id = in_guild_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58312
f
get_inventory_data
in_inventory_id bigint
dune.inventorydata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_inventory_data(in_inventory_id bigint)
 RETURNS dune.inventorydata
 LANGUAGE plpgsql
AS $function$
DECLARE
    inventory_data InventoryData;
BEGIN
	SELECT INTO
        inventory_data.inventory_id, inventory_data.inventory_type, inventory_data.max_item_count, inventory_data.max_item_volume
        id, inventory_type, max_item_count, max_item_volume
    FROM inventories
    WHERE id = in_inventory_id;

	RETURN inventory_data;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58313
f
get_inventory_id
in_actor_id bigint, in_component_name_hash integer
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_inventory_id(in_actor_id bigint, in_component_name_hash integer)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	inv_id BIGINT;
BEGIN
	SELECT INTO inv_id inventory_id FROM actor_inventories ai JOIN inventories i ON (ai.inventory_id = i.id) WHERE i.actor_id = in_actor_id AND ai.component_name_hash = in_component_name_hash;
	IF inv_id IS NULL THEN
		INSERT INTO inventories("id", "actor_id") VALUES(DEFAULT, in_actor_id) RETURNING id INTO inv_id;
		INSERT INTO actor_inventories("inventory_id", "component_name_hash") VALUES(inv_id, in_component_name_hash);
	END IF;
	RETURN inv_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58314
f
get_items_to_remove
items_to_remove text[]
text[]

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_items_to_remove(items_to_remove text[])
 RETURNS text[]
 LANGUAGE plpgsql
AS $function$
declare
    result text[];
begin
    select array(
        select unnest(items_to_remove)
        except
        select name
        from removed_items
    ) into result;
    return result;
end;
$function$

@@@ENDRTN@@@
@@@RTN@@@58315
f
get_landclaim_segments
in_totem_id bigint
TABLE(grid_location_x bigint, grid_location_y bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_landclaim_segments(in_totem_id bigint)
 RETURNS TABLE(grid_location_x bigint, grid_location_y bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT t.grid_location_x, t.grid_location_y
    FROM landclaim_segments AS t
    WHERE t.totem_id = in_totem_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58316
f
get_learned_building_sets
in_account_id bigint
SETOF text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_learned_building_sets(in_account_id bigint)
 RETURNS SETOF text
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT UNNEST(learned_building_sets) FROM building_progression WHERE account_id = in_account_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58317
f
get_learned_new_buildable_pieces
in_account_id bigint
SETOF text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_learned_new_buildable_pieces(in_account_id bigint)
 RETURNS SETOF text
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT UNNEST(new_buildable_pieces) FROM building_progression WHERE account_id = in_account_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58318
f
get_login_journey_nodes
in_account_id bigint
dune.journeynodeinfo[]

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_login_journey_nodes(in_account_id bigint)
 RETURNS dune.journeynodeinfo[]
 LANGUAGE plpgsql
AS $function$
DECLARE
    result JourneyNodeInfo[];
BEGIN
    SELECT
        array_agg(
            (
             res.story_node_id,
             res.override_reward_block,
             res.has_pending_reward,
             res.complete_condition_state,
             res.reveal_condition_state,
             res.fail_condition_state,
             res.metadata_state,
             res.reset_group
            )::JourneyNodeInfo
        )
    INTO result
    FROM journey_story_node res
    WHERE res.account_id = in_account_id;

    RETURN result;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58319
f
get_login_journey_nodes_cooldown
in_account_id bigint
dune.journeynodecooldowninfo[]

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_login_journey_nodes_cooldown(in_account_id bigint)
 RETURNS dune.journeynodecooldowninfo[]
 LANGUAGE plpgsql
AS $function$
DECLARE
    result JourneyNodeCooldownInfo[];
BEGIN
    SELECT
        array_agg(
            (
             res.story_node_id,
             res.time_to_expire
            )::JourneyNodeCooldownInfo
        )
    INTO result
    FROM journey_story_node_cooldown res
    WHERE res.account_id = in_account_id;

    RETURN result;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58320
f
get_mnemonic_recall_lessons
in_account_id bigint
TABLE(id bigint, lesson_id text, lession_state bigint, lesson_progress integer, is_new boolean)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_mnemonic_recall_lessons(in_account_id bigint)
 RETURNS TABLE(id bigint, lesson_id text, lession_state bigint, lesson_progress integer, is_new boolean)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT t.id, t.lesson_id, t.lesson_state, t.lesson_progress, t.is_new
    FROM mnemonic_recall as t
    WHERE t.account_id = in_account_id
    ORDER BY t.id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58321
f
get_online_player_controller_ids
in_map text
SETOF bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_online_player_controller_ids(in_map text)
 RETURNS SETOF bigint
 LANGUAGE plpgsql
AS $function$
BEGIN
    return query SELECT DISTINCT ps.player_controller_id
        FROM player_state ps
        JOIN actors a ON (a.id = ps.player_controller_id)
        WHERE ps.online_status = 'Online' AND a.map = in_map;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58322
f
get_online_player_controller_ids_on_farm

SETOF bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_online_player_controller_ids_on_farm()
 RETURNS SETOF bigint
 LANGUAGE plpgsql
AS $function$
BEGIN
    return query SELECT DISTINCT ps.player_controller_id
        FROM player_state ps
        JOIN actors a ON (a.id = ps.player_controller_id)
        WHERE ps.online_status = 'Online';
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58323
f
get_partition_presets

SETOF text
Adds a partition only if its unique. Not using constraints, as this is only a helper function.
@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_partition_presets()
 RETURNS SETOF text
 LANGUAGE plpgsql
AS $function$
BEGIN
	return query SELECT routine_name::text as preset_function FROM information_schema.routines WHERE routine_type = 'FUNCTION' and routine_name ilike 'initialize_partitions_%';
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58324
f
get_partitions
in_map text
SETOF bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_partitions(in_map text)
 RETURNS SETOF bigint
 LANGUAGE plpgsql
AS $function$
begin
	SELECT partition_id FROM world_partition where map = in_map order by partition_id ASC;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58325
f
get_party_members
in_party_id bigint
TABLE(player_id bigint, fls_id text, party_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_party_members(in_party_id bigint)
 RETURNS TABLE(player_id bigint, fls_id text, party_id bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	SELECT pm.player_id, acc.user, pm.party_id
	FROM party_members pm
	LEFT JOIN player_state ps ON ps.player_controller_id = pm.player_id
	LEFT JOIN accounts acc ON acc.id = ps.account_id
	WHERE pm.party_id = in_party_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58326
f
get_permission_actors_for_server
in_server_info dune.serverinfo
SETOF dune.actorpermissioncombineddata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_permission_actors_for_server(in_server_info dune.serverinfo)
 RETURNS SETOF dune.actorpermissioncombineddata
 LANGUAGE plpgsql
AS $function$
BEGIN
	return query
		with ids as (
			select array_agg(actors.id) as ids
				from permission_actor join actors on actors.id = permission_actor.actor_id
				where server_info_match(actors, in_server_info) and actors.owner_account_id is null
		)
		select permissions.* from ids, get_permission_for_actors(ids.ids) as permissions;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58327
f
get_permission_for_actor
in_actor_id bigint
dune.actorpermissioncombineddata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_permission_for_actor(in_actor_id bigint)
 RETURNS dune.actorpermissioncombineddata
 LANGUAGE plpgsql
AS $function$
BEGIN
	return (select get_permission_for_actors(array[in_actor_id]) limit 1);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58328
f
get_permission_for_actors
in_actor_id bigint[]
SETOF dune.actorpermissioncombineddata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_permission_for_actors(in_actor_id bigint[])
 RETURNS SETOF dune.actorpermissioncombineddata
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
		SELECT
			-- ActorPermissionData
            ROW(
                ROW(
                    permission_actor.actor_id,
                    permission_actor.actor_name,
                    actors.class,
                    permission_actor.actor_type,
                    permission_actor.access_level,
                    permission_actor.is_child
                )::ActorPermissionEntry,
                array_agg(
                    ROW(
                        permission_actor_rank.rank,
                        permission_actor_rank.player_id
                    )::ActorPermissionRankData
                ) FILTER (WHERE permission_actor_rank.player_id IS NOT NULL),
                array_agg(guild_members.guild_id) FILTER (WHERE permission_actor_rank.player_id IS NOT NULL)
            )::ActorPermissionData AS data,

            -- ActorPermissionLocationData
            ROW(
                actors.id,
                actors.partition_id,
                actors.map,
                actors.dimension_index,
                actors.transform
            )::ActorPermissionLocationData AS loc
		FROM
			permission_actor
			LEFT JOIN permission_actor_rank on permission_actor.actor_id = permission_actor_rank.permission_actor_id
			LEFT JOIN guild_members ON guild_members.player_id = permission_actor_rank.player_id
			LEFT JOIN actors ON actors.id = permission_actor.actor_id
		WHERE
			permission_actor.actor_id = ANY(in_actor_id)
		GROUP BY
			permission_actor.actor_id,
			actors.id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58329
f
get_permission_for_player_actors
in_player_id bigint, in_min_rank smallint
SETOF dune.actorpermissioncombineddata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_permission_for_player_actors(in_player_id bigint, in_min_rank smallint)
 RETURNS SETOF dune.actorpermissioncombineddata
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	WITH player_owned_actors AS
	(
		SELECT array_agg(permission_actor_id) AS ids
		FROM permission_actor_rank join actors on actors.id = permission_actor_rank.permission_actor_id
		WHERE permission_actor_rank.player_id = in_player_id AND permission_actor_rank.rank <= in_min_rank and actors.owner_account_id is null
	)
	SELECT permissions.* FROM player_owned_actors, get_permission_for_actors(player_owned_actors.ids) AS permissions;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58330
f
get_placeable_id
in_actor_id bigint, in_class text, in_building_type text
dune.placeablegetidcomposite

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_placeable_id(in_actor_id bigint, in_class text, in_building_type text)
 RETURNS dune.placeablegetidcomposite
 LANGUAGE plpgsql
AS $function$
DECLARE
	placeable_id BIGINT;
BEGIN
	SELECT INTO placeable_id id FROM placeables WHERE "id" = in_actor_id;

	IF placeable_id IS NULL THEN
		SELECT assign_actor_id(in_class) id INTO placeable_id;
		INSERT INTO placeables("id", "building_type") VALUES(placeable_id, in_building_type);
	END IF;

    return ROW(placeable_id)::PlaceableGetIdComposite;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58331
f
get_player_access_codes
in_account_id bigint
TABLE(access_code integer, access_code_type integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_access_codes(in_account_id bigint)
 RETURNS TABLE(access_code integer, access_code_type integer)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT p.access_code, p.access_code_type
    FROM player_access_codes AS p
    WHERE p.account_id = in_account_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58332
f
get_player_current_faction_reputation
in_actor_id bigint, OUT out_faction_id smallint, OUT out_reputation_amount integer
record

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_current_faction_reputation(in_actor_id bigint, OUT out_faction_id smallint, OUT out_reputation_amount integer)
 RETURNS record
 LANGUAGE plpgsql
AS $function$
BEGIN
	SELECT
		pf.faction_id,
		COALESCE(pfr.reputation_amount, 0)
	INTO out_faction_id, out_reputation_amount
	FROM player_faction pf
	LEFT JOIN player_faction_reputation pfr
		ON pfr.actor_id = pf.actor_id AND pfr.faction_id = pf.faction_id
	WHERE pf.actor_id = in_actor_id
	limit 1;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58333
f
get_player_faction
in_player_id bigint, in_neutral_faction_id smallint
smallint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_faction(in_player_id bigint, in_neutral_faction_id smallint)
 RETURNS smallint
 LANGUAGE plpgsql
AS $function$
DECLARE 
	player_faction_id SMALLINT;
BEGIN
	SELECT player_faction.faction_id INTO player_faction_id
	FROM player_faction
	WHERE actor_id = in_player_id;

	IF player_faction_id IS NULL THEN
		player_faction_id := in_neutral_faction_id;
	END IF;

	RETURN player_faction_id;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58334
f
get_player_faction_name
in_actor_id bigint, OUT player_faction_name text, OUT utc_time_faction_change timestamp without time zone
record

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_faction_name(in_actor_id bigint, OUT player_faction_name text, OUT utc_time_faction_change timestamp without time zone)
 RETURNS record
 LANGUAGE plpgsql
AS $function$
BEGIN
	SELECT factions.name, player_faction.utc_time_faction_change AT TIME ZONE 'UTC' INTO player_faction_name, utc_time_faction_change
	FROM factions INNER JOIN player_faction ON factions.id = player_faction.faction_id 
	WHERE player_faction.actor_id = in_actor_id 
	limit 1;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58335
f
get_player_guild_invites
in_player_id bigint
TABLE(invite_id bigint, guild_id bigint, guild_name text, guild_description text, sender_player_id bigint, invite_sent_timespan bigint, character_name text, sender_character_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_guild_invites(in_player_id bigint)
 RETURNS TABLE(invite_id bigint, guild_id bigint, guild_name text, guild_description text, sender_player_id bigint, invite_sent_timespan bigint, character_name text, sender_character_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT guild_invites.invite_id, guilds.guild_id, guilds.guild_name, guilds.guild_description, guild_invites.sender_player_id, guild_invites.invite_sent_timespan, player_state.character_name, sender_player_state.character_name AS sender_character_name
	FROM guild_invites
	JOIN guilds ON guilds.guild_id = guild_invites.guild_id
	JOIN player_state ON player_state.player_controller_id = guild_invites.player_id
	JOIN player_state AS sender_player_state ON sender_player_state.player_controller_id = guild_invites.sender_player_id
	WHERE player_id = in_player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58336
f
get_player_ids_online_state
in_player_ids bigint[]
SETOF dune.playeronlinestateentry

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_ids_online_state(in_player_ids bigint[])
 RETURNS SETOF dune.playeronlinestateentry
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT actors.id, player_state.character_name, (actors.map, actors.partition_id, actors.dimension_index)::ServerInfo, (player_state.last_avatar_activity AT TIME ZONE 'UTC')::TIMESTAMP, player_state.online_status
	FROM actors
	JOIN player_state ON player_state.player_controller_id = actors.id
	WHERE actors.id = ANY(in_player_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58337
f
get_player_infos_for_actor_ids
in_actor_ids bigint[]
TABLE(player_id bigint, character_name text, fls_id text, funcom_id text, platform_id text, platform_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_infos_for_actor_ids(in_actor_ids bigint[])
 RETURNS TABLE(player_id bigint, character_name text, fls_id text, funcom_id text, platform_id text, platform_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT ps.player_controller_id, ps.character_name, acc.user, acc.funcom_id, acc.platform_id, acc.platform_name
	FROM accounts acc LEFT JOIN player_state ps ON acc.id=ps.account_id
	WHERE ps.player_controller_id = ANY(in_actor_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58338
f
get_player_infos_for_character_names
in_character_names text[]
TABLE(player_id bigint, character_name text, fls_id text, funcom_id text, platform_id text, platform_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_infos_for_character_names(in_character_names text[])
 RETURNS TABLE(player_id bigint, character_name text, fls_id text, funcom_id text, platform_id text, platform_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT ps.player_controller_id, ps.character_name, acc.user, acc.funcom_id, acc.platform_id, acc.platform_name
	FROM accounts acc LEFT JOIN player_state ps ON acc.id=ps.account_id
	WHERE ps.character_name = ANY(in_character_names);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58339
f
get_player_infos_for_fls_ids
in_fls_ids text[]
TABLE(player_id bigint, character_name text, fls_id text, funcom_id text, platform_id text, platform_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_infos_for_fls_ids(in_fls_ids text[])
 RETURNS TABLE(player_id bigint, character_name text, fls_id text, funcom_id text, platform_id text, platform_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT ps.player_controller_id, ps.character_name, acc.user, acc.funcom_id, acc.platform_id, acc.platform_name
	FROM accounts acc LEFT JOIN player_state ps ON acc.id=ps.account_id
	WHERE acc.user = ANY(in_fls_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58340
f
get_player_infos_for_funcom_ids
in_funcom_ids text[]
TABLE(player_id bigint, character_name text, fls_id text, funcom_id text, platform_id text, platform_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_infos_for_funcom_ids(in_funcom_ids text[])
 RETURNS TABLE(player_id bigint, character_name text, fls_id text, funcom_id text, platform_id text, platform_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT ps.player_controller_id, ps.character_name, acc.user, acc.funcom_id, acc.platform_id, acc.platform_name
	FROM accounts acc LEFT JOIN player_state ps ON acc.id=ps.account_id
	WHERE acc.funcom_id = ANY(in_funcom_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58341
f
get_player_online_state_within_grace_period_for_each_server

TABLE(fls_id text, previous_partition_id bigint, current_server_id text, online_status dune.playerconnectionstatus, within_grace_period boolean, last_disconnect timestamp without time zone, demo_playtime_seconds integer, logoff_persistence_end_time timestamp without time zone, party_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_online_state_within_grace_period_for_each_server()
 RETURNS TABLE(fls_id text, previous_partition_id bigint, current_server_id text, online_status dune.playerconnectionstatus, within_grace_period boolean, last_disconnect timestamp without time zone, demo_playtime_seconds integer, logoff_persistence_end_time timestamp without time zone, party_id bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY SELECT accounts.user as fls_id, player_state.previous_server_partition_id as previous_partition_id, player_state.server_id as current_server_id, player_state.online_status as online_status, player_state.reconnect_grace_period_end > (now() AT TIME ZONE 'UTC')::TIMESTAMP as within_grace_period, (player_state.last_avatar_activity AT TIME ZONE 'UTC')::TIMESTAMP as last_disconnect, demo_users.demo_playtime_seconds, (player_state.logoff_persistence_end_time)::TIMESTAMP as logoff_persistence_end_time, party_members.party_id
    FROM player_state
    LEFT JOIN accounts ON accounts.id = player_state.account_id
    LEFT JOIN demo_users ON accounts.user = demo_users.fls_id
    LEFT JOIN party_members ON player_state.player_controller_id = party_members.player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58342
f
get_player_owned_vehicles_data
in_player_id bigint, in_account_id bigint
TABLE(out_actor_id bigint, out_name text, out_class text, out_map text, out_partition_id bigint, out_dimension integer, out_transform dune.transform, out_actor_state text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_owned_vehicles_data(in_player_id bigint, in_account_id bigint)
 RETURNS TABLE(out_actor_id bigint, out_name text, out_class text, out_map text, out_partition_id bigint, out_dimension integer, out_transform dune.transform, out_actor_state text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY WITH owned_actors AS(
		SELECT (entry).actor_id, (entry).actor_name FROM get_permission_for_player_actors(in_player_id, 1::smallint) -- 1:owner
			WHERE (entry).actor_type = 2 -- 2:vehicles
		UNION SELECT vehicle_id, vehicle_name FROM recovered_vehicles -- note: recovered vehicles are removed from permission actors
			WHERE account_id = in_account_id
	) SELECT owned_actors.actor_id, actor_name, class, map, partition_id, dimension_index, transform, (actor_state).state::text
		FROM owned_actors LEFT JOIN actors ON actors.id = owned_actors.actor_id LEFT JOIN actor_state ON actor_state.actor_id = owned_actors.actor_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58343
f
get_player_partition_id
in_fls_id text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_partition_id(in_fls_id text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
BEGIN
    -- Only the player pawn actually travels and gets it's partition id updated
    return (
        select actors.partition_id
        from accounts as acc
        join player_state as ps on ps.account_id = acc.id
        join actors on actors.id = ps.player_pawn_id
        where acc.user = in_fls_id
    );
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58344
f
get_player_pawn
in_account_id bigint
TABLE(description dune.actordescription, server_info dune.serverinfo, player_tags text[])

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_pawn(in_account_id bigint)
 RETURNS TABLE(description dune.actordescription, server_info dune.serverinfo, player_tags text[])
 LANGUAGE plpgsql
AS $function$
BEGIN
    return query
        with id as (select player_pawn_id as id from player_state where account_id = in_account_id limit 1),
        tags as (select array_agg(tag) from player_tags where account_id = in_account_id)
            select load_full_actors(array[id]) as description, get_actor_server_info(id) as server_info, (select * from tags) as player_tags
            from id limit 1;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58345
f
get_player_virtual_currency_balances
in_controller_id bigint
TABLE(out_currency_id smallint, out_currency_balance bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_player_virtual_currency_balances(in_controller_id bigint)
 RETURNS TABLE(out_currency_id smallint, out_currency_balance bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
	return query (
		with currencies as (select currency_id, balance from player_virtual_currency_balances where player_controller_id = in_controller_id),
		bad_currencies as (select * from currencies where balance < 0),
		target_account_id as (select account_id from player_state where player_controller_id = in_controller_id limit 1),
		report_cheaters as (select currency_id, flag_player_as_cheater(target_account_id.account_id, 'negative_solaris') from bad_currencies, target_account_id),
		fix_bad_currencies as (update player_virtual_currency_balances set balance = 0 from report_cheaters where player_controller_id = in_controller_id and player_virtual_currency_balances.currency_id = report_cheaters.currency_id)
		select currency_id, balance from currencies
	);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58346
f
get_players_demo_data
in_controller_ids bigint[]
SETOF dune.playerdemostatedescription

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_players_demo_data(in_controller_ids bigint[])
 RETURNS SETOF dune.playerdemostatedescription
 LANGUAGE plpgsql
AS $function$
BEGIN
 	RETURN QUERY SELECT ps.player_controller_id, (ps.last_avatar_activity AT TIME ZONE 'UTC')::TIMESTAMP, demo_playtime_seconds, demo_state
	FROM encrypted_accounts acc
	JOIN player_state ps ON ps.account_id = acc.id
	JOIN demo_users du ON acc.user = du.fls_id
    WHERE ps.player_controller_id = ANY (in_controller_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58347
f
get_recipes_to_remove
recipes_to_remove text[]
text[]

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_recipes_to_remove(recipes_to_remove text[])
 RETURNS text[]
 LANGUAGE plpgsql
AS $function$
declare
    result text[];
begin
    select array(
        select unnest(recipes_to_remove)
        except
        select name
        from removed_recipes
    ) into result;
    return result;
end;
$function$

@@@ENDRTN@@@
@@@RTN@@@58348
f
get_registered_spawned_actor
in_spawner_id bigint
SETOF bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_registered_spawned_actor(in_spawner_id bigint)
 RETURNS SETOF bigint
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
	SELECT actor_id FROM actor_spawner_actors WHERE spawner_id = in_spawner_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58349
f
get_respawn_locations
in_account_id bigint
dune.respawnlocation[]

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_respawn_locations(in_account_id bigint)
 RETURNS dune.respawnlocation[]
 LANGUAGE plpgsql
AS $function$
DECLARE
    result RespawnLocation[];
BEGIN
    SELECT
        array_agg(
            (res.id,
             (
                 CASE
                     WHEN res.locator_transform IS NOT NULL THEN 'Transform'
                     WHEN res.locator_actor_id IS NOT NULL THEN 'PersistentActor'
                     WHEN res.locator_name IS NOT NULL THEN 'StaticLocatorName'
                 END::SpawnLocatorType,
                 res.locator_transform,
                 res.locator_actor_id,
                 res.locator_name,
                 res.locator_name_index
             )::SpawnLocatorDescriptor,
             res.map,
             res.dimension,
             res.last_used_timestamp,
             res.group
            )::RespawnLocation
        )
    INTO result
    FROM player_respawn_locations res
    WHERE res.account_id = in_account_id;

    RETURN result;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58350
f
get_schema_version

integer

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_schema_version()
 RETURNS integer
 LANGUAGE plpgsql
AS $function$
BEGIN
	return 999999;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58351
f
get_solaris_id

smallint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_solaris_id()
 RETURNS smallint
 LANGUAGE plpgsql
 IMMUTABLE
AS $function$
DECLARE
    solaris_id CONSTANT SMALLINT := 0;
BEGIN
    return solaris_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58352
f
get_spawner_id
in_map text, in_name text, in_dimension_index integer
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_spawner_id(in_map text, in_name text, in_dimension_index integer)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	spawner_id BIGINT;
BEGIN
    SELECT INTO spawner_id "id" FROM actor_spawners WHERE map = in_map AND name = in_name AND dimension_index = in_dimension_index;
    IF spawner_id IS NULL THEN
        INSERT INTO actor_spawners("map", "name", "dimension_index") VALUES(in_map, in_name, in_dimension_index) ON CONFLICT DO NOTHING RETURNING "id" INTO spawner_id;
        IF spawner_id IS NULL THEN
            SELECT INTO spawner_id "id" FROM actor_spawners WHERE map = in_map AND name = in_name AND dimension_index = in_dimension_index;
        END IF;
    END IF;
    RETURN spawner_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58353
f
get_stored_user_data_encryption_key_hash

bytea

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_stored_user_data_encryption_key_hash()
 RETURNS bytea
 LANGUAGE sql
 IMMUTABLE
AS $function$select null::bytea;$function$

@@@ENDRTN@@@
@@@RTN@@@58354
f
get_stored_user_data_encryption_status

dune.userdataencryptionstatus

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_stored_user_data_encryption_status()
 RETURNS dune.userdataencryptionstatus
 LANGUAGE sql
 IMMUTABLE
AS $function$select 'Disabled'::UserDataEncryptionStatus$function$

@@@ENDRTN@@@
@@@RTN@@@58355
f
get_stored_user_data_encryption_taint_xmax

bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_stored_user_data_encryption_taint_xmax()
 RETURNS bigint
 LANGUAGE sql
 IMMUTABLE
AS $function$select null::int8;$function$

@@@ENDRTN@@@
@@@RTN@@@58356
f
get_sub_inventory_id
in_owner_item_id bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_sub_inventory_id(in_owner_item_id bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	inv_id BIGINT;
BEGIN
	SELECT INTO inv_id id FROM inventories WHERE item_id = in_owner_item_id;
	IF inv_id IS NULL THEN
		INSERT INTO inventories("id", "item_id") VALUES(DEFAULT, in_owner_item_id) RETURNING id INTO inv_id;
	END IF;
	RETURN inv_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58357
f
get_traveling_actor_id_and_types
in_actor_id bigint
TABLE(id bigint, is_instigator boolean, is_player boolean, level integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_traveling_actor_id_and_types(in_actor_id bigint)
 RETURNS TABLE(id bigint, is_instigator boolean, is_player boolean, level integer)
 LANGUAGE plpgsql
AS $function$
begin
	return query
	select t.id, t.is_instigator, (ps.player_pawn_id is not null) as is_player, t.level
	from get_traveling_actor_ids(in_actor_id) as t
	left join player_state as ps
	on t.id = ps.player_pawn_id
	order by t.level, t.id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58358
f
get_traveling_actor_ids
in_actor_id bigint, in_max_recursion_level integer
TABLE(id bigint, is_instigator boolean, level integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_traveling_actor_ids(in_actor_id bigint, in_max_recursion_level integer DEFAULT 5)
 RETURNS TABLE(id bigint, is_instigator boolean, level integer)
 LANGUAGE plpgsql
AS $function$
begin
	return query
	with recursive
		-- go down the dependency tree and gather all the parents
        p(id, lvl) AS (
				select a.id, 0
				from actors as a
				where (in_actor_id = a.id)
			union all
				select ap.parent_id, (p.lvl - 1)
				from p, travel_actor_parent as ap
				where (p.id = ap.id) and (p.lvl > -in_max_recursion_level)
		),
		-- find the most distant parent (root)
		r as (
			select * from p	order by p.lvl limit 1
		),
		-- go up the tree from the root and gather all the children
		t(id, ins, lvl) as (
				select r.id, true, 0 from r
			union all
				select ap.id, ap.is_instigator, (t.lvl + 1)
				from t, travel_actor_parent as ap
				where (t.id = ap.parent_id) and (t.lvl < 2 * in_max_recursion_level)
		)
	select * from t order by t.lvl, id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58359
f
get_traveling_actors_fls_ids
in_actor_id bigint
TABLE(out_id text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_traveling_actors_fls_ids(in_actor_id bigint)
 RETURNS TABLE(out_id text)
 LANGUAGE plpgsql
AS $function$
begin
	return query
	select a.user
	from get_traveling_actor_ids(in_actor_id) as t
	inner join player_state as ps
		on t.id = ps.player_pawn_id
	inner join accounts as a
		on a.id = ps.account_id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58360
f
get_traveling_non_player_actor_ids
in_actor_id bigint
TABLE(id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_traveling_non_player_actor_ids(in_actor_id bigint)
 RETURNS TABLE(id bigint)
 LANGUAGE plpgsql
AS $function$
begin
	return query
	select t.id
	from get_traveling_actor_ids(in_actor_id) as t
	left join player_state as ps
		on t.id = ps.player_pawn_id
	left join travel_actor_parent as ap
		on ap.id = in_actor_id
	where
			ps.player_pawn_id is null
		and ap.is_instigator is true
	order by t.level, t.id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58361
f
get_unbacked_up_vehicle_ids_for_account
in_account_id bigint
TABLE(vehicle_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_unbacked_up_vehicle_ids_for_account(in_account_id bigint)
 RETURNS TABLE(vehicle_id bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
        SELECT v.id
		FROM vehicles v
		JOIN permission_actor_rank par ON v.id = par.permission_actor_id
		JOIN player_state ps ON par.player_id = ps.player_controller_id
		left join backup_vehicles bv ON v.id = bv.vehicle_id
		WHERE ps.account_id = in_account_id
			AND bv.vehicle_id IS NULL;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58362
f
get_universe_time

TABLE(universe_time_timestamp timestamp without time zone, down_time_accumulation bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_universe_time()
 RETURNS TABLE(universe_time_timestamp timestamp without time zone, down_time_accumulation bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT farm_variables.universe_time_timestamp, farm_variables.down_time_accumulation from farm_variables;
	RETURN;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58363
f
get_unsaved_base_totem_ids_for_account
in_account_id bigint
TABLE(totem_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_unsaved_base_totem_ids_for_account(in_account_id bigint)
 RETURNS TABLE(totem_id bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
		SELECT t.id
		FROM totems t
		JOIN permission_actor_rank par ON t.id = par.permission_actor_id
		JOIN player_state ps ON par.player_id = ps.player_controller_id
		LEFT JOIN base_backup_linked_actors bbla ON t.id = bbla.actor_id
		WHERE ps.account_id = in_account_id
			AND bbla.actor_id IS NULL;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58364
f
get_vehicle_id
in_actor_id bigint, in_class text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_vehicle_id(in_actor_id bigint, in_class text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	vehicle_id BIGINT;
BEGIN
	SELECT INTO vehicle_id id FROM vehicles WHERE "id" = in_actor_id;
	IF vehicle_id IS NULL THEN
		SELECT assign_actor_id(in_class) id INTO vehicle_id;
		INSERT INTO vehicles("id") VALUES(vehicle_id);
	END IF;
	RETURN vehicle_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58365
f
get_vehicle_module_inventory_id
in_vehicle_module_id bigint, in_vehicle_module_inventory_type integer
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.get_vehicle_module_inventory_id(in_vehicle_module_id bigint, in_vehicle_module_inventory_type integer)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	inv_id BIGINT;
BEGIN
	SELECT INTO inv_id inventory_id FROM vehicle_module_inventories vi JOIN inventories i ON (vi.inventory_id = i.id) WHERE i.vehicle_module_id = in_vehicle_module_id AND vi.vehicle_module_inventory_type = in_vehicle_module_inventory_type;
	IF inv_id IS NULL THEN
		INSERT INTO inventories("id", "vehicle_module_id") VALUES(DEFAULT, in_vehicle_module_id) RETURNING id INTO inv_id;
		INSERT INTO vehicle_module_inventories("inventory_id", "vehicle_module_inventory_type") VALUES(inv_id, in_vehicle_module_inventory_type);
	END IF;
	RETURN inv_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58366
f
guild_handle_actor_delete
in_player_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.guild_handle_actor_delete(in_player_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_guild_id BIGINT;
	out_new_leader_id BIGINT;
	out_guild_count INT;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	-- remove invites
	PERFORM reject_guild_invite(invite_id) FROM guild_invites
	where player_id = in_player_id OR sender_player_id = in_player_id;

	-- Get member guild id
	SELECT guild_id INTO out_guild_id FROM guild_members WHERE player_id = in_player_id;
	IF FOUND THEN

		SELECT INTO out_guild_count COUNT(*) FROM guild_members WHERE guild_id = out_guild_id;

		IF out_guild_count < 2 THEN
			PERFORM disband_guild(out_guild_id);
		ELSE
			-- Promote new leder
			IF is_player_guild_admin(in_player_id, out_guild_id) THEN
				SELECT player_id into out_new_leader_id from guild_members
				WHERE guild_id = out_guild_id AND player_id <> in_player_id
				LIMIT 1;
				IF out_new_leader_id IS NOT NULL THEN
					PERFORM promote_guild_member(out_guild_id, out_new_leader_id, 100::smallint);
				END IF;
			END IF;
			-- remove member
			PERFORM remove_guild_members(ARRAY[in_player_id], out_guild_id, 0::smallint);
		END IF;
	END IF;

END
$function$

@@@ENDRTN@@@
@@@RTN@@@58367
f
guilds_get_exclusive_operation_lock

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.guilds_get_exclusive_operation_lock()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM pg_advisory_xact_lock(601145);  -- GUILDS in leet :/
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58368
f
handle_player_faction_guild_effects
in_player_id bigint, in_faction_id smallint, neutral_faction_id smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.handle_player_faction_guild_effects(in_player_id bigint, in_faction_id smallint, neutral_faction_id smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	guild_member_record record;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	SELECT * INTO guild_member_record
	FROM guild_members
	JOIN guilds ON guilds.guild_id = guild_members.guild_id
	WHERE player_id = in_player_id;

	IF guild_member_record IS NOT NULL THEN
		PERFORM pg_notify('guild_notify_channel', format('player_guild_data_changed#{"GuildId" : %s , "PlayerId" : %s, "FactionId" : %s}', guild_member_record.guild_id, in_player_id, in_faction_id));
	 	IF guild_member_record.guild_faction != neutral_faction_id THEN
			-- If guild leader changes faction and guild already has a non neutral faction, break the guild allegiance
			IF is_player_guild_admin(in_player_id, guild_member_record.guild_id) THEN
				PERFORM break_guild_allegiance(guild_member_record.guild_id, neutral_faction_id);
			-- Neutral player changing to Faction A while Guild is Faction B must be kicked
			ELSEIF guild_member_record.guild_faction != in_faction_id AND in_faction_id != neutral_faction_id THEN
				PERFORM remove_guild_members(ARRAY[in_player_id], guild_member_record.guild_id, 2::smallint);
			END IF;
		END IF;
	END IF;
	PERFORM clean_guild_invites_with_incompatible_faction(in_player_id, in_faction_id, neutral_faction_id);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58369
f
igwo_delete_world_partitions
in_partition_ids bigint[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.igwo_delete_world_partitions(in_partition_ids bigint[])
 RETURNS void
 LANGUAGE sql
AS $function$
	delete from world_partition where partition_id = any(in_partition_ids);
$function$

@@@ENDRTN@@@
@@@RTN@@@58370
f
igwo_get_partition_id_seq_last_value

bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.igwo_get_partition_id_seq_last_value()
 RETURNS bigint
 LANGUAGE sql
AS $function$
	select last_value from world_partition_partition_id_seq;
$function$

@@@ENDRTN@@@
@@@RTN@@@58371
f
igwo_get_partition_ids

SETOF bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.igwo_get_partition_ids()
 RETURNS SETOF bigint
 LANGUAGE sql
AS $function$
	select partition_id from world_partition order by partition_id asc;
$function$

@@@ENDRTN@@@
@@@RTN@@@58372
f
igwo_get_partitions

TABLE(partition_id bigint, map text, dimension_index integer, label text, min_x double precision, min_y double precision, max_x double precision, max_y double precision)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.igwo_get_partitions()
 RETURNS TABLE(partition_id bigint, map text, dimension_index integer, label text, min_x double precision, min_y double precision, max_x double precision, max_y double precision)
 LANGUAGE sql
AS $function$
	SELECT
		partition_id,
		map,
		dimension_index,
		label,
		(partition_definition->'box'->>'min_x')::float8,
		(partition_definition->'box'->>'min_y')::float8,
		(partition_definition->'box'->>'max_x')::float8,
		(partition_definition->'box'->>'max_y')::float8
	FROM world_partition
	ORDER BY partition_id ASC;
$function$

@@@ENDRTN@@@
@@@RTN@@@58373
f
igwo_get_server_details

TABLE(address text, server_id text, ready boolean, partition_id bigint, map text, dimension_index integer, label text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.igwo_get_server_details()
 RETURNS TABLE(address text, server_id text, ready boolean, partition_id bigint, map text, dimension_index integer, label text)
 LANGUAGE sql
AS $function$
	select
		host(fs.igw_addr)||':'||fs.igw_port as address,
		fs.server_id,
		fs.ready,
		wp.partition_id,
		wp.map,
		wp.dimension_index,
		wp.label
	from get_farm_state() fs
	left join world_partition wp on wp.server_id = fs.server_id
	where fs.server_id is not null;
$function$

@@@ENDRTN@@@
@@@RTN@@@58374
f
igwo_insert_world_partition
in_partition_id bigint, in_map text, in_partition_definition jsonb, in_dimension_index integer, in_partition_label text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.igwo_insert_world_partition(in_partition_id bigint, in_map text, in_partition_definition jsonb, in_dimension_index integer DEFAULT 0, in_partition_label text DEFAULT NULL::text)
 RETURNS bigint
 LANGUAGE sql
AS $function$
	insert into world_partition(partition_id, map, partition_definition, dimension_index, label)
	values (
		in_partition_id,
		in_map,
		in_partition_definition,
		in_dimension_index,
		coalesce(in_partition_label, determine_partition_label(in_map, in_dimension_index, null, false, in_partition_id)))
	returning partition_id;
$function$

@@@ENDRTN@@@
@@@RTN@@@58375
f
igwo_next_partition_id_seq

bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.igwo_next_partition_id_seq()
 RETURNS bigint
 LANGUAGE sql
AS $function$
	select nextval('world_partition_partition_id_seq');
$function$

@@@ENDRTN@@@
@@@RTN@@@58376
f
igwo_notify_world_partition_update

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.igwo_notify_world_partition_update()
 RETURNS void
 LANGUAGE sql
AS $function$
	notify world_partition_update;
$function$

@@@ENDRTN@@@
@@@RTN@@@58377
f
igwo_restart_partition_id_seq
in_restart_with bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.igwo_restart_partition_id_seq(in_restart_with bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	-- Use execute to substitute the numeric parameter into the alter sequence command
	execute format('alter sequence world_partition_partition_id_seq restart with %s', in_restart_with);
end;
$function$

@@@ENDRTN@@@
@@@RTN@@@58378
f
igwo_update_world_partition
in_map text, in_partition_definition jsonb, in_partition_id bigint, in_dimension_index integer, in_label text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.igwo_update_world_partition(in_map text, in_partition_definition jsonb, in_partition_id bigint, in_dimension_index integer, in_label text DEFAULT NULL::text)
 RETURNS void
 LANGUAGE sql
AS $function$
	update world_partition
	set map = in_map,
		partition_definition = in_partition_definition,
		dimension_index = in_dimension_index,
		label = coalesce(in_label, label)
	where partition_id = in_partition_id;
$function$

@@@ENDRTN@@@
@@@RTN@@@58379
f
init_event_log
in_partition_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.init_event_log(in_partition_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    threshold_ts TIMESTAMPTZ;
	last_ts TIMESTAMPTZ;
	cleanup_threshold_days CONSTANT INTEGER := 14;
BEGIN
	-- calculate threshold
	threshold_ts := now() - (cleanup_threshold_days * interval '1 day');

	-- lock table
	LOCK TABLE event_log_maintanence IN EXCLUSIVE MODE;

	SELECT last_cleanup
	INTO last_ts
	FROM event_log_maintanence;

	IF last_ts < threshold_ts THEN    
		-- delete events older then the passed in threshold
		DELETE FROM event_log
		WHERE event_time < threshold_ts;

		-- Update last_cleanup in event_log_maintanence
		UPDATE event_log_maintanence
		SET last_cleanup = now();
	END IF;

	-- update partition_id
	PERFORM set_config('dune.partition_id', in_partition_id::TEXT, false);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58380
f
initialize_partitions_basic_battlegroup

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.initialize_partitions_basic_battlegroup()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('SH_HarkoVillage', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('SH_HarkoVillage', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('SH_Arrakeen', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('SH_Arrakeen', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('DeepDesert_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('DeepDesert_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('DeepDesert_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 2);
	perform add_partition_unique('Overmap', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform update_partition_labels();
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58381
f
initialize_partitions_basic_survival_1

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.initialize_partitions_basic_survival_1()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 2);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 3);
	perform update_partition_labels();
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58382
f
initialize_partitions_development_battlegroup

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.initialize_partitions_development_battlegroup()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	-- Current battlegroup maps
	perform initialize_partitions_full_battlegroup();

	-- Core development maps
	perform initialize_partitions_editor_default_1x1();
	perform initialize_partitions_igw_test_small_2x2();
	perform initialize_partitions_igw_training();

	-- Additional Gyms
	perform add_partition_unique('CombatGym_01', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0, 'CombatGym_01');
	perform add_partition_unique('Audio_Gym', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0, 'Audio_Gym');
	perform add_partition_unique('CombatGym_Camps', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0, 'CombatGym_Camps');
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58383
f
initialize_partitions_editor_default_1x1

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.initialize_partitions_editor_default_1x1()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	perform add_partition_unique('Editor_Default', '{"type": "box2d_array", "boxes": [{"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}]}', 0, 'Editor_Default');
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58384
f
initialize_partitions_full_battlegroup

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.initialize_partitions_full_battlegroup()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 2);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 3);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 4);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 5);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 6);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 7);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 8);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 9);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 10);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 11);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 12);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 13);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 14);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 15);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 16);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 17);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 18);
	perform add_partition_unique('Survival_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 19);
	perform add_partition_unique('SH_HarkoVillage', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('SH_HarkoVillage', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('SH_Arrakeen', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('SH_Arrakeen', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('SH_FallenLight', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('SH_FallenLight', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('CB_Story_Hephaestus', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Story_Ecolab_Carthag', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Story_WaterFatManor', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('DeepDesert_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('DeepDesert_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('DeepDesert_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 2);
	perform add_partition_unique('Overmap', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('Story_ProcesVerbal', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('DLC_Story_LostHarvest', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('DLC_Story_LostHarvest_EcolabA', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('DLC_Story_LostHarvest_EcolabB', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('DLC_Story_LostHarvest_ForgottenLab', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('Story_ArtOfKanly', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('Story_HeighlinerDungeon', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Dungeon_Hephaestus', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Dungeon_OldCarthag', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Story_BanditFortress01', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Overland_S_05', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Overland_S_06', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Overland_S_04', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Overland_M_01', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Overland_S_07', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('Story_Faction_Outpost_Hark', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('Story_Faction_Outpost_Atre', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Ecolab_Bronze_Green_089', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Ecolab_Bronze_Green_152', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Ecolab_Bronze_Green_195', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Ecolab_Bronze_Green_024', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Ecolab_Bronze_Green_136', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('PolarCap_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('PolarCap_1', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 1);
	perform add_partition_unique('CB_Story_DestroyedZanovar', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Story_OrbitalMonitor', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Dungeon_TheFacility', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Dungeon_ThePit', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform add_partition_unique('CB_Overland_S_08', '{"box": {"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0}, "type": "box2d_array"}', 0);
	perform update_partition_labels();
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58385
f
initialize_partitions_igw_test_small_2x1

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.initialize_partitions_igw_test_small_2x1()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	perform add_partition_unique('IGW_Test_Small', '{"type": "box2d_array", "boxes": [{"max_x": 0.5, "max_y": 1, "min_x": 0, "min_y": 0}]}', 0, 'IGW_Test_Small_A1');
	perform add_partition_unique('IGW_Test_Small', '{"type": "box2d_array", "boxes": [{"max_x": 1, "max_y": 1, "min_x": 0.5, "min_y": 0}]}', 0, 'IGW_Test_Small_A2');
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58386
f
initialize_partitions_igw_test_small_2x2

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.initialize_partitions_igw_test_small_2x2()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	perform add_partition_unique('IGW_Test_Small', '{"type": "box2d_array", "boxes": [{"max_x": 0.5, "max_y": 0.5, "min_x": 0, "min_y": 0}]}', 0, 'IGW_Test_Small_A1');
	perform add_partition_unique('IGW_Test_Small', '{"type": "box2d_array", "boxes": [{"max_x": 0.5, "max_y": 1, "min_x": 0, "min_y": 0.5}]}', 0, 'IGW_Test_Small_A2');
	perform add_partition_unique('IGW_Test_Small', '{"type": "box2d_array", "boxes": [{"max_x": 1, "max_y": 0.5, "min_x": 0.5, "min_y": 0}]}', 0, 'IGW_Test_Small_B1');
	perform add_partition_unique('IGW_Test_Small', '{"type": "box2d_array", "boxes": [{"max_x": 1, "max_y": 1, "min_x": 0.5, "min_y": 0.5}]}', 0, 'IGW_Test_Small_B2');
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58387
f
initialize_partitions_igw_training

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.initialize_partitions_igw_training()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	perform add_partition_unique('IGW_Training', '{"type": "box2d_array", "boxes": [{"max_x": 1, "max_y": 0.33333, "min_x": 0, "min_y": 0}]}', 0, 'IGW_Training_A1');
	perform add_partition_unique('IGW_Training', '{"type": "box2d_array", "boxes": [{"max_x": 1, "max_y": 0.66667, "min_x": 0, "min_y": 0.33333}]}', 0, 'IGW_Training_A2');
	perform add_partition_unique('IGW_Training', '{"type": "box2d_array", "boxes": [{"max_x": 1, "max_y": 1, "min_x": 0, "min_y": 0.66667}]}', 0, 'IGW_Training_A3');
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58388
f
initialize_specialization_keystones
in_keystones text[]
TABLE(keystone_id smallint, keystone_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.initialize_specialization_keystones(in_keystones text[])
 RETURNS TABLE(keystone_id smallint, keystone_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	LOCK TABLE specialization_keystones_map IN SHARE ROW EXCLUSIVE MODE;

-- Note: we filter the existing values before the insert, otherwise it bumps the generated id in specialization_keystones_map 
	INSERT INTO specialization_keystones_map (name)
		SELECT in_keystone_name FROM UNNEST(in_keystones) in_keystone_name LEFT JOIN specialization_keystones_map k ON in_keystone_name = k.name
		WHERE name IS NULL;
	RETURN QUERY SELECT * from specialization_keystones_map;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58389
f
initialize_world_partition
in_map_name text, in_num_servers integer, in_dimension_index integer
SETOF bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.initialize_world_partition(in_map_name text, in_num_servers integer, in_dimension_index integer DEFAULT 0)
 RETURNS SETOF bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
	return query
		with _cleanup as (
			DELETE FROM world_partition WHERE map = in_map_name and dimension_index = in_dimension_index
		)
		INSERT INTO world_partition (map, partition_definition, dimension_index, label)
			select in_map_name, format('{"type": "cell_index", "index": %s}', generate_series)::JSONB, in_dimension_index, in_map_name || '_' || in_dimension_index || '_' || generate_series
			from generate_series(0, in_num_servers - 1)
			returning partition_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58390
f
interact_get_vendor_items_bought_from_player
in_vendor_id text, in_player_id bigint, in_current_cycle_start_timestamp bigint
TABLE(out_template_id text, out_amount_bought integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.interact_get_vendor_items_bought_from_player(in_vendor_id text, in_player_id bigint, in_current_cycle_start_timestamp bigint)
 RETURNS TABLE(out_template_id text, out_amount_bought integer)
 LANGUAGE plpgsql
AS $function$
DECLARE
   player_timestamp BIGINT;
BEGIN
	-- Clean items bought by player if the vendor's cycle was reset since the last time they interacted with it
	IF EXISTS
		(SELECT * FROM vendor_stock_cycle
		WHERE vendor_id = in_vendor_id AND player_id = in_player_id AND last_interacted_timestamp < in_current_cycle_start_timestamp)
	THEN
		DELETE FROM vendor_stock_state WHERE vendor_id = in_vendor_id AND player_id = in_player_id;	
	END IF;
	
	PERFORM update_vendor_timestamp_for_player(in_vendor_id, in_player_id, in_current_cycle_start_timestamp);
	
	RETURN QUERY
    SELECT template_id, amount_bought FROM vendor_stock_state WHERE vendor_id = in_vendor_id AND player_id = in_player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58391
f
internal_add_party_member
in_invite_id bigint, in_party_id bigint, in_player_id bigint, in_platform_session_id text, in_platform_name text, in_max_party_member_count integer
dune.partyacceptinviteresult

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.internal_add_party_member(in_invite_id bigint, in_party_id bigint, in_player_id bigint, in_platform_session_id text, in_platform_name text, in_max_party_member_count integer)
 RETURNS dune.partyacceptinviteresult
 LANGUAGE plpgsql
AS $function$
DECLARE
	party_exists BOOLEAN;
	member_count INTEGER;
	out_platform_members_count INTEGER;
	out_player_name TEXT;
	out_accept_error PartyAcceptInviteResult DEFAULT 'Success'::PartyAcceptInviteResult;
BEGIN

	-- check if party exists
	SELECT INTO party_exists EXISTS (SELECT 1 FROM parties WHERE party_id = in_party_id);
	IF NOT party_exists THEN
		PERFORM remove_party_invite(in_invite_id, 2::smallint); -- PartyNoLongerExists = 2
		RAISE NOTICE 'Trying to add player % to non existing party %.', in_player_id, in_party_id;
		out_accept_error = 'NonExistingParty'::PartyAcceptInviteResult;
		RETURN out_accept_error;
	END IF;

	-- check party member count
	SELECT INTO member_count COUNT(*) FROM party_members WHERE party_id = in_party_id;
	IF member_count >= in_max_party_member_count THEN
		PERFORM remove_party_invite(in_invite_id, 1::smallint); -- PartyFull = 1
		RAISE NOTICE 'Trying to add more members than the allowed % to party %.', in_max_party_member_count, in_party_id;
		out_accept_error = 'PartyFull'::PartyAcceptInviteResult;
		RETURN out_accept_error;
	END IF;

	-- insert member
	INSERT INTO party_members("player_id", "party_id") VALUES(in_player_id, in_party_id);

    -- track platform information
    SELECT num_of_players INTO out_platform_members_count FROM platform_parties_mapping WHERE platform_name = in_platform_name AND dune_party_id = in_party_id;
    IF out_platform_members_count IS NOT NULL THEN
        -- there was a platform session for the player's party
		UPDATE platform_parties_mapping SET num_of_players = out_platform_members_count+1 WHERE platform_name = in_platform_name AND dune_party_id = in_party_id;
    ELSE
		-- no mapping for this platform yet, add
		INSERT INTO platform_parties_mapping ("platform_session_id", "platform_name", "dune_party_id", "num_of_players")
		SELECT in_platform_session_id, in_platform_name, in_party_id, 1
		WHERE in_platform_session_id <> '' AND in_platform_name <> '';
    END IF;

	-- Get player name
	SELECT player_state.character_name INTO out_player_name
		FROM player_state WHERE player_state.player_controller_id = in_player_id;

	PERFORM remove_party_invite(in_invite_id, 0::smallint); -- Silent = 0

	PERFORM pg_notify('party_notify_channel', format(
		'add_party_member#{"PartyId" : %s, "PlayerId" : %s, "PlayerName" : "%s", "PlayerPlatformName" : "%s", "PlayerPlatformSessionId" : "%s"}', 
		in_party_id, in_player_id, out_player_name, in_platform_name, in_platform_session_id));

	RETURN out_accept_error;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58392
f
internal_create_party
in_invite_id bigint, in_leader_id bigint, in_leader_platform_session_id text, in_leader_platform_name text, in_member_id bigint, in_platform_session_id text, in_platform_name text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.internal_create_party(in_invite_id bigint, in_leader_id bigint, in_leader_platform_session_id text, in_leader_platform_name text, in_member_id bigint, in_platform_session_id text, in_platform_name text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	leader_registered BOOLEAN;
	players_belong_to_party BOOLEAN;
	out_party_id BIGINT;
	out_player_name TEXT;
	out_leader_name TEXT;
BEGIN

	-- Check if the leader already exists in the parties table
	SELECT INTO leader_registered EXISTS (SELECT 1 FROM parties WHERE party_leader_id = in_leader_id);
	IF leader_registered THEN
		RAISE EXCEPTION 'Leader already has a party.';
	END IF;

	-- Check if either the leader or member already exists in the party_members table
	SELECT INTO players_belong_to_party EXISTS (SELECT 1 FROM party_members WHERE player_id = in_leader_id OR player_id = in_member_id);
	IF players_belong_to_party THEN
		RAISE EXCEPTION 'One of the players is already in a party.';
	END IF;

	-- If neither condition is met, insert the new party and members
	INSERT INTO parties ("party_leader_id") VALUES (in_leader_id) RETURNING party_id INTO out_party_id;
	INSERT INTO party_members (player_id, party_id) VALUES (in_leader_id, out_party_id), (in_member_id, out_party_id);

	-- Update all of the leaders invites to have the new party as party id
	UPDATE party_invites SET party_id = out_party_id WHERE sender_player_id = in_leader_id;

	-- Get leader name
	SELECT player_state.character_name INTO out_leader_name
		FROM player_state WHERE player_state.player_controller_id = in_leader_id;

	-- Get member name
	SELECT player_state.character_name INTO out_player_name
		FROM player_state WHERE player_state.player_controller_id = in_member_id;

	-- Handle platform sessions mapping for new party
	IF in_leader_platform_name = in_platform_name THEN
		-- If players are from the same platform and leader has session id (console), we create mapping (if their platform_name and session_id are valid)
		INSERT INTO platform_parties_mapping ("platform_session_id", "platform_name", "dune_party_id", "num_of_players")
		SELECT in_leader_platform_session_id, in_leader_platform_name, out_party_id, 2
		WHERE in_leader_platform_session_id <> '' AND in_leader_platform_name <> '';
	ELSE
		-- Create leader's platform session mapping if their platform_name and session_id are valid
		INSERT INTO platform_parties_mapping ("platform_session_id", "platform_name", "dune_party_id", "num_of_players")
		SELECT in_leader_platform_session_id, in_leader_platform_name, out_party_id, 1
		WHERE in_leader_platform_session_id <> '' AND in_leader_platform_name <> '';

		-- Create member's platform session mapping if their platform_name and session_id are valid
		INSERT INTO platform_parties_mapping ("platform_session_id", "platform_name", "dune_party_id", "num_of_players")
		SELECT in_platform_session_id, in_platform_name, out_party_id, 1
		WHERE in_platform_session_id <> '' AND in_platform_name <> '';
	END IF;

	PERFORM remove_party_invite(in_invite_id, 0::smallint); -- Silent = 0

	PERFORM pg_notify('party_notify_channel', format(
		'create_party#{"PartyId" : %s, "LeaderId" : %s, "LeaderName" : "%s", "LeaderPlatformName" : "%s", "LeaderPlatformSessionId" : "%s", "MemberId" : %s, "MemberName" : "%s", "MemberPlatformName" : "%s", "MemberPlatformSessionId" : "%s"}',
		out_party_id, in_leader_id, out_leader_name, in_leader_platform_name, in_leader_platform_session_id, in_member_id, out_player_name, in_platform_name, in_platform_session_id));

	RETURN out_party_id;

END
$function$

@@@ENDRTN@@@
@@@RTN@@@58393
f
is_player_guild_admin
in_player_id bigint, in_guild_id bigint
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.is_player_guild_admin(in_player_id bigint, in_guild_id bigint)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
DECLARE
	found_role_id SMALLINT;
BEGIN
	SELECT role_id FROM guild_members WHERE player_id = in_player_id AND guild_id = in_guild_id INTO found_role_id;
	IF NOT FOUND THEN
    	RETURN FALSE;
	END IF;
	RETURN found_role_id = 100;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58394
f
is_player_offline
in_fls_id text
boolean
Return true if player is marked as offline, taking into account server crashing before players online state was updated in DB.
@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.is_player_offline(in_fls_id text)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
declare
    has_state int;
    is_offline boolean;
begin
    -- If there's no player_state row for this account, treat the player as offline
    select count(*) into has_state
    from player_state ps
    join accounts a on a.id = ps.account_id
    where a.user = in_fls_id;

    if has_state = 0 then
        return true;
    end if;

    select exists(
        select 1
        from player_state ps
        join accounts a on a.id = ps.account_id
        where a.user = in_fls_id
          and (
            ps.online_status = 'Offline'
            -- Player is treated as offline if last played server is offline/unavailable, or not set at all
            or (ps.server_id is null or ps.server_id not in (select * from active_server_ids))
          )
    ) into is_offline;

    return is_offline;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58395
f
is_player_party_leader
in_player_id bigint, in_party_id bigint
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.is_player_party_leader(in_player_id bigint, in_party_id bigint)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
DECLARE
	found_leader_id SMALLINT;
BEGIN
	SELECT party_leader_id FROM parties WHERE party_id = in_party_id INTO found_leader_id;
	IF NOT FOUND THEN
		RETURN FALSE;
	END IF;
	RETURN found_leader_id = in_player_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58396
f
join_platform_session_party
in_leader_platform_id text, in_player_platform_id text, in_platform_session_id text, in_platform_name text, in_max_party_member_count integer
dune.partyacceptinviteresult

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.join_platform_session_party(in_leader_platform_id text, in_player_platform_id text, in_platform_session_id text, in_platform_name text, in_max_party_member_count integer)
 RETURNS dune.partyacceptinviteresult
 LANGUAGE plpgsql
AS $function$
DECLARE
    out_party_id BIGINT;
	out_accept_error PartyAcceptInviteResult DEFAULT 'Success'::PartyAcceptInviteResult;
    out_leader_id BIGINT;
    out_player_id BIGINT;
BEGIN
    IF in_platform_name IS NULL OR length(in_platform_name) = 0 THEN
        RAISE EXCEPTION 'platform_name must not be empty';
    END IF;
    IF in_platform_session_id IS NULL OR length(in_platform_session_id) = 0 THEN
        RAISE EXCEPTION 'platform_session_id must not be empty';
    END IF;

    -- Fetch party id, but do nothing with it
    SELECT dune_party_id INTO out_party_id
    FROM platform_parties_mapping
    WHERE platform_session_id = in_platform_session_id
      AND platform_name = in_platform_name
    LIMIT 1;

	out_leader_id = get_controller_id_from_platform_id(in_leader_platform_id);
	out_player_id = get_controller_id_from_platform_id(in_player_platform_id);

	IF out_party_id IS NULL THEN
		-- Using same platform name and platform session id cause they are joining through system invite, they'll always be the same platform.
		PERFORM internal_create_party(NULL, out_leader_id, in_platform_session_id, in_platform_name,
			out_player_id, in_platform_session_id, in_platform_name);
	ELSE
		out_accept_error := internal_add_party_member(NULL, out_party_id, out_player_id, in_platform_session_id, in_platform_name, in_max_party_member_count);
	END IF;

	RETURN out_accept_error;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58397
f
journey_story_node_cooldown_add
in_account_id bigint, in_story_node_id text, in_time_to_expire timestamp without time zone
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.journey_story_node_cooldown_add(in_account_id bigint, in_story_node_id text, in_time_to_expire timestamp without time zone)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO journey_story_node_cooldown(account_id, story_node_id, time_to_expire)
	VALUES(in_account_id, in_story_node_id, in_time_to_expire);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58398
f
journey_story_node_cooldown_delete_expired
in_time_to_check timestamp without time zone
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.journey_story_node_cooldown_delete_expired(in_time_to_check timestamp without time zone)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM journey_story_node_cooldown WHERE time_to_expire < in_time_to_check;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58399
f
landsraad_cast_vote
in_term_id bigint, in_player_id bigint, in_decree_name text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_cast_vote(in_term_id bigint, in_player_id bigint, in_decree_name text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	term_winning_faction_id SMALLINT = NULL;
	elected_decree_id BIGINT = NULL;
	player_guild_id BIGINT = NULL;
	guild_faction_id SMALLINT = NULL;
	voting_decree_id BIGINT = NULL;
	voting_influence INTEGER = 0;
	guild_ids_json JSON = NULL;
BEGIN
	LOCK TABLE landsraad_decree_votes IN EXCLUSIVE MODE;

	SELECT term.winning_faction_id, term.elected_decree_id FROM landsraad_decree_term AS term WHERE term.term_id = in_term_id INTO term_winning_faction_id, elected_decree_id;

	IF term_winning_faction_id IS NULL THEN
		RAISE EXCEPTION 'Cannot insert landsraad vote, term % has no winning faction yet', in_term_id;
	END IF;

	IF elected_decree_id IS NOT NULL THEN
		RAISE EXCEPTION 'Cannot insert landsraad vote, term % already has an elected decree', in_term_id;
	END IF;

	SELECT guilds.guild_id, guilds.guild_faction FROM guild_members 
		JOIN guilds ON guild_members.guild_id = guilds.guild_id WHERE guild_members.player_id = in_player_id INTO player_guild_id, guild_faction_id;

	IF player_guild_id IS NULL THEN
		RAISE EXCEPTION 'Cannot insert landsraad vote, player % not in guild', in_player_id;
	END IF;

	IF guild_faction_id != term_winning_faction_id THEN
		RAISE EXCEPTION 'Cannot insert landsraad vote, guild % not alligned to winning faction %', player_guild_id, term_winning_faction_id;
	END IF;

	IF is_player_guild_admin(in_player_id, player_guild_id) = FALSE THEN
		RAISE EXCEPTION 'Cannot insert landsraad vote, player % is not guild admin of guild %', in_player_id, player_guild_id;
	END IF;

	IF EXISTS (SELECT FROM landsraad_decree_votes AS votes WHERE votes.guild_id = player_guild_id) THEN
		RAISE WARNING 'Cannot insert landsraad vote, guild % has voted already', player_guild_id;
		RETURN;
	END IF;

	SELECT decrees.id FROM landsraad_decree_rotation AS rotation 
		INNER JOIN landsraad_decrees AS decrees ON rotation.decree_id = decrees.id
		WHERE decrees.decree_name = in_decree_name INTO voting_decree_id;

	IF voting_decree_id IS NULL THEN
		RAISE EXCEPTION 'Cannot insert landsraad vote, decree % not for election', in_decree_name;
	END IF;

	SELECT FLOOR(landsraad_load_guild_contribution(in_term_id, player_guild_id, term_winning_faction_id))::INTEGER INTO voting_influence;

	IF voting_influence IS NULL THEN
		RAISE WARNING 'Cannot insert landsraad vote, guild % has no contribution', player_guild_id;
		RETURN;
	END IF;

	INSERT INTO landsraad_decree_votes VALUES(voting_decree_id, player_guild_id, in_player_id, voting_influence);

	SELECT json_agg(player_guild_id) INTO guild_ids_json;
	PERFORM pg_notify('landsraad_notify_channel', format('guild_vote_changed#{"GuildIds": %s}', guild_ids_json));

END $function$

@@@ENDRTN@@@
@@@RTN@@@58400
f
landsraad_change_term_end_time
end_term_id bigint, new_end_time timestamp without time zone, in_test_term boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_change_term_end_time(end_term_id bigint, new_end_time timestamp without time zone, in_test_term boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	LOCK TABLE landsraad_decree_term IN EXCLUSIVE MODE;
	UPDATE landsraad_decree_term SET test_term = in_test_term WHERE term_id = end_term_id AND test_term = false;
	UPDATE landsraad_decree_term SET end_time = new_end_time AT TIME ZONE 'UTC' WHERE term_id = end_term_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58401
f
landsraad_check_task_completion

trigger

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_check_task_completion()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
DECLARE
	task_completed BOOLEAN = FALSE;
BEGIN
	-- if a faction reached the tasks's goal amount set the task as completed
	WITH faction_progress AS (SELECT landsraad_task_faction_contributions.task_id, SUM(landsraad_task_faction_contributions.amount) AS amount  FROM landsraad_task_faction_contributions 
	WHERE landsraad_task_faction_contributions.task_id = NEW.task_id AND faction_id = NEW.faction_id GROUP BY faction_id, landsraad_task_faction_contributions.task_id)
		SELECT COALESCE (faction_progress.amount, 0) >= landsraad_tasks.goal_amount 
		FROM landsraad_tasks LEFT JOIN faction_progress ON landsraad_tasks.id = faction_progress.task_id
		WHERE landsraad_tasks.id = NEW.task_id
		INTO task_completed;

	IF task_completed THEN
		UPDATE landsraad_tasks SET completed = TRUE, winning_faction_id = NEW.faction_id, completion_time = NOW() WHERE id = NEW.task_id AND completed = FALSE;
		PERFORM pg_notify('landsraad_notify_channel', 'state_changed');
	END IF;

	RETURN NULL;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58402
f
landsraad_check_term_won

trigger

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_check_term_won()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
DECLARE
	row_completed BIGINT[] = NULL;
	column_completed BIGINT[] = NULL;
	diagonal_1_completed BIGINT[] = NULL; 
	diagonal_2_completed BIGINT[] = NULL;
	sysselraad_amount INTEGER = 0;
BEGIN
	-- check for sysselrad rule (a row, a column or a diagonal completed by a faction)
	WITH board AS (SELECT task.id as task_id, task.winning_faction_id, task.board_index / 5 AS row, task.board_index % 5 AS col, task.sysselraad FROM landsraad_tasks task WHERE task.term_id = NEW.term_id)
	SELECT 
		(SELECT array_agg(board.task_id) FROM board WHERE board.row = ANY(SELECT board.row FROM board WHERE board.winning_faction_id = NEW.winning_faction_id GROUP BY (board.row) HAVING COUNT(board.col) = 5)),
		(SELECT array_agg(board.task_id) FROM board WHERE board.col = ANY(SELECT board.col FROM board WHERE board.winning_faction_id = NEW.winning_faction_id GROUP BY (board.col) HAVING COUNT(board.row) = 5)),
		(SELECT CASE WHEN COUNT(board.winning_faction_id) = 5 THEN array_agg(board.task_id) END FROM ( VALUES (0, 0), (1, 1), (2, 2), (3, 3), (4, 4) ) AS t(row, col) JOIN board ON board.row = t.row AND board.col = t.col WHERE board.winning_faction_id = NEW.winning_faction_id),
		(SELECT CASE WHEN COUNT(board.winning_faction_id) = 5 THEN array_agg(board.task_id) END FROM ( VALUES (0, 4), (1, 3), (2, 2), (3, 1), (4, 0) ) AS t(row, col) JOIN board ON board.row = t.row AND board.col = t.col WHERE board.winning_faction_id = NEW.winning_faction_id),
		(SELECT COUNT(*) FROM board WHERE board.sysselraad)
	INTO row_completed, column_completed, diagonal_1_completed, diagonal_2_completed, sysselraad_amount;

	IF sysselraad_amount = 0 AND (row_completed IS NOT NULL OR column_completed IS NOT NULL OR diagonal_1_completed IS NOT NULL OR diagonal_2_completed IS NOT NULL) THEN
		UPDATE landsraad_tasks SET sysselraad = TRUE WHERE id = ANY(row_completed || column_completed || diagonal_1_completed || diagonal_2_completed);
		UPDATE landsraad_decree_term SET winning_faction_id = NEW.winning_faction_id WHERE term_id = NEW.term_id AND winning_faction_id IS NULL;
		PERFORM pg_notify('landsraad_notify_channel', 'state_changed');
	END IF;

	RETURN NULL;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58403
f
landsraad_collect_task_telemetry_for_faction
in_term_id bigint, in_faction_name text
TABLE(task_telemetry dune.landsraadtermtasktelemetry[])

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_collect_task_telemetry_for_faction(in_term_id bigint, in_faction_name text)
 RETURNS TABLE(task_telemetry dune.landsraadtermtasktelemetry[])
 LANGUAGE plpgsql
AS $function$
DECLARE
	current_faction_id SMALLINT = NULL;
BEGIN
	SELECT id FROM factions WHERE factions.name = in_faction_name INTO current_faction_id;

	RETURN query SELECT ARRAY_AGG((in_faction_name, landsraad_tasks.house_name, task_reveal.revealed, (CASE WHEN winning_faction_id = current_faction_id THEN TRUE ELSE FALSE END), task_progress.participant_count, CAST(landsraad_tasks.board_index AS INTEGER), landsraad_tasks.completion_time)::LandsraadTermTaskTelemetry)
		FROM landsraad_tasks 
		LEFT JOIN landsraad_task_reveal_state task_reveal
            ON task_reveal.task_id = landsraad_tasks.id AND task_reveal.faction_id = current_faction_id
		LEFT JOIN LATERAL (
	        SELECT COUNT(DISTINCT ltpc.player_id) AS participant_count FROM landsraad_task_player_contributions ltpc WHERE ltpc.task_id = landsraad_tasks.id and ltpc.faction_id = current_faction_id GROUP BY task_id
        ) AS task_progress ON true
		WHERE landsraad_tasks.term_id = in_term_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58404
f
landsraad_collect_term_telemetry
in_term_id bigint, in_faction_names text[]
TABLE(term_telemetry dune.landsraadtermtelemetry[], task_telemetry dune.landsraadtermtasktelemetry[])

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_collect_term_telemetry(in_term_id bigint, in_faction_names text[])
 RETURNS TABLE(term_telemetry dune.landsraadtermtelemetry[], task_telemetry dune.landsraadtermtasktelemetry[])
 LANGUAGE plpgsql
AS $function$
DECLARE
	faction_name TEXT = NULL;
	term_telemetry LandsraadTermTelemetry[];
	task_telemetry LandsraadTermTaskTelemetry[];
BEGIN
	FOREACH faction_name IN ARRAY in_faction_names
	LOOP
		term_telemetry = ARRAY_APPEND(term_telemetry, landsraad_collect_term_telemetry_for_faction(in_term_id, faction_name));
		task_telemetry = ARRAY_CAT(task_telemetry, landsraad_collect_task_telemetry_for_faction(in_term_id, faction_name));
	END LOOP;
	
	RETURN query SELECT term_telemetry, task_telemetry;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58405
f
landsraad_collect_term_telemetry_for_faction
in_term_id bigint, in_faction_name text
dune.landsraadtermtelemetry

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_collect_term_telemetry_for_faction(in_term_id bigint, in_faction_name text)
 RETURNS dune.landsraadtermtelemetry
 LANGUAGE plpgsql
AS $function$
DECLARE
	current_faction_id SMALLINT = NULL;
	winning_faction_id SMALLINT = NULL;
	start_time TIMESTAMPTZ = NULL;
	end_time TIMESTAMPTZ = NULL;
	sysselraad_count INTEGER = NULL;
	term_result TEXT = NULL;
	faction_won BOOLEAN = FALSE;
	participants_num_faction INTEGER = NULL;
	tasks_completed INTEGER = NULL;
	tasks_revealed INTEGER = NULL;
BEGIN
	SELECT id FROM factions WHERE factions.name = in_faction_name INTO current_faction_id;

	SELECT term.winning_faction_id, term.start_time, term.end_time FROM landsraad_decree_term AS term WHERE term_id = in_term_id INTO winning_faction_id, start_time, end_time;
	
	IF winning_faction_id IS NULL THEN
		term_result = 'TIE';
	ELSE
		SELECT COUNT(id) FROM landsraad_tasks WHERE term_id = in_term_id AND sysselraad = true INTO sysselraad_count;
		IF sysselraad_count = 5 THEN
			term_result = 'SYSSELRAAD';
		ELSE
			term_result = 'TASK_COUNT';
		END IF;
	END IF;

	IF winning_faction_id = current_faction_id THEN
		faction_won = true;
	END IF;
	
	SELECT COUNT(DISTINCT player_id) FROM landsraad_task_player_contributions LEFT JOIN landsraad_tasks ON
		landsraad_task_player_contributions.task_id = landsraad_tasks.id 
		WHERE landsraad_tasks.term_id = in_term_id AND landsraad_task_player_contributions.faction_id = current_faction_id
		INTO participants_num_faction;
	
	SELECT COUNT(id) FROM landsraad_tasks WHERE landsraad_tasks.term_id = in_term_id
		AND landsraad_tasks.winning_faction_id = current_faction_id AND landsraad_tasks.completed = true
		INTO tasks_completed;
		
	SELECT COUNT(DISTINCT landsraad_tasks.id) FROM landsraad_task_reveal_state LEFT JOIN landsraad_tasks ON
		landsraad_task_reveal_state.task_id = landsraad_tasks.id
		WHERE landsraad_tasks.term_id = in_term_id AND landsraad_task_reveal_state.faction_id = current_faction_id AND landsraad_task_reveal_state.revealed = true
		INTO tasks_revealed;
		
	RETURN (in_faction_name, term_result, faction_won, participants_num_faction, tasks_completed, tasks_revealed, start_time, end_time)::LandsraadTermTelemetry;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58406
f
landsraad_collect_vote_telemetry
in_term_id bigint, in_winning_faction_id integer
TABLE(guild_id bigint, decree_name text, voting_influence integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_collect_vote_telemetry(in_term_id bigint, in_winning_faction_id integer)
 RETURNS TABLE(guild_id bigint, decree_name text, voting_influence integer)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN query SELECT guild_contribution.guild_id, landsraad_decrees.decree_name, FLOOR(SUM(guild_contribution.amount))::INTEGER
			FROM landsraad_tasks AS tasks 
			INNER JOIN landsraad_task_guild_contributions AS guild_contribution 
			ON guild_contribution.task_id = tasks.id AND guild_contribution.faction_id = tasks.winning_faction_id AND tasks.term_id = in_term_id AND guild_contribution.faction_id = in_winning_faction_id
			LEFT JOIN landsraad_decree_votes 
			ON landsraad_decree_votes.guild_id = guild_contribution.guild_id
			LEFT JOIN landsraad_decrees 
			ON landsraad_decree_votes.decree_id = landsraad_decrees.id
			GROUP BY (guild_contribution.guild_id, landsraad_decrees.decree_name);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58407
f
landsraad_collect_votes
in_term_id bigint
TABLE(elected_decree text, winning_faction_name text, available_decrees text[], guild_votes dune.landsraadguildvotetelemetry[])

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_collect_votes(in_term_id bigint)
 RETURNS TABLE(elected_decree text, winning_faction_name text, available_decrees text[], guild_votes dune.landsraadguildvotetelemetry[])
 LANGUAGE plpgsql
AS $function$
DECLARE
	has_elected_decree BOOL = FALSE;
	has_winning_faction BOOL = FALSE;
	winning_decree_id BIGINT = NULL;
    winning_decree_name TEXT = NULL;
	winning_faction_name TEXT = NULL;
    winning_faction_id INT = NULL;
	available_decrees TEXT[];
    vote_telemetry LandsraadGuildVoteTelemetry[];
BEGIN
	LOCK TABLE landsraad_decree_term, landsraad_decree_rotation, landsraad_decree_votes IN EXCLUSIVE MODE;

	SELECT CASE WHEN landsraad_decree_term.elected_decree_id IS NULL THEN FALSE ELSE TRUE END, 
		   CASE WHEN landsraad_decree_term.winning_faction_id IS NULL THEN FALSE ELSE TRUE END
		FROM landsraad_decree_term WHERE term_id = in_term_id INTO has_elected_decree, has_winning_faction;
		
	IF has_winning_faction IS FALSE THEN
		RETURN query SELECT NULL, NULL, available_decrees, vote_telemetry;
		RETURN;
	END IF;
	
	SELECT factions.name, term.winning_faction_id FROM landsraad_decree_term AS term LEFT JOIN factions ON term.winning_faction_id = factions.id WHERE term.term_id = in_term_id INTO winning_faction_name, winning_faction_id;

	SELECT ARRAY_AGG(landsraad_decrees.decree_name) FROM landsraad_decree_rotation INNER JOIN landsraad_decrees ON landsraad_decree_rotation.decree_id = landsraad_decrees.id INTO available_decrees;
	
	SELECT ARRAY_AGG((guild_id, decree_name, voting_influence)::LandsraadGuildVoteTelemetry) FROM landsraad_collect_vote_telemetry(in_term_id, winning_faction_id) INTO vote_telemetry;

	-- Only resolve votes if the latest term has no elected decree
	IF has_elected_decree IS FALSE THEN
		WITH
			votes AS (SELECT decree_id, SUM(influence) AS amount FROM landsraad_decree_votes GROUP BY decree_id), 
			max_votes AS (SELECT MAX(amount) AS amount FROM votes)
		UPDATE landsraad_decree_term
			SET elected_decree_id = 
				CASE WHEN (SELECT amount FROM max_votes) IS NOT NULL THEN
					(SELECT decree_id FROM votes WHERE amount = (SELECT amount FROM max_votes) ORDER BY RANDOM() LIMIT 1)
				ELSE
					(SELECT decree_id FROM landsraad_decree_rotation ORDER BY RANDOM() LIMIT 1)
				END
			WHERE term_id = in_term_id
			returning elected_decree_id INTO winning_decree_id;
	ELSE
		SELECT term.elected_decree_id FROM landsraad_decree_term term ORDER BY term_id DESC LIMIT 1 INTO winning_decree_id;
	END IF;

	SELECT decree_name FROM landsraad_decrees WHERE id = winning_decree_id INTO winning_decree_name;
	
	RETURN query SELECT winning_decree_name, winning_faction_name, available_decrees, vote_telemetry;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58408
f
landsraad_determine_winner
in_term_id bigint
text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_determine_winner(in_term_id bigint)
 RETURNS text
 LANGUAGE plpgsql
AS $function$
DECLARE
	has_winning_faction BOOLEAN = FALSE;
	winning_faction_name TEXT = NULL;
BEGIN
	SELECT CASE WHEN winning_faction_id IS NULL THEN FALSE ELSE TRUE END FROM landsraad_decree_term WHERE term_id = in_term_id INTO has_winning_faction;
	
	-- only set winning faction if not set already (sysselraad has been secured)
	IF NOT has_winning_faction THEN
		WITH tasks_completed_by_faction AS (SELECT winning_faction_id AS faction, COUNT(id) AS num_tasks FROM landsraad_tasks WHERE term_id = in_term_id AND winning_faction_id IS NOT NULL GROUP BY (winning_faction_id)),
			winner_count AS (SELECT COUNT(faction) AS amount FROM tasks_completed_by_faction WHERE num_tasks = (SELECT MAX(num_tasks) FROM tasks_completed_by_faction) GROUP BY(num_tasks)),
			winner AS (SELECT faction FROM tasks_completed_by_faction ORDER BY num_tasks DESC LIMIT 1)
		UPDATE landsraad_decree_term SET winning_faction_id = CASE WHEN winner_count.amount = 1 THEN winner.faction ELSE NULL END FROM winner, winner_count;
		PERFORM pg_notify('landsraad_notify_channel', 'state_changed');
	END IF;

	SELECT factions.name FROM landsraad_decree_term AS term LEFT JOIN factions ON term.winning_faction_id = factions.id WHERE term.term_id = in_term_id INTO winning_faction_name;
	
	RETURN winning_faction_name;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58409
f
landsraad_force_end_term
end_term_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_force_end_term(end_term_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	PERFORM landsraad_change_term_end_time(end_term_id, now() AT TIME ZONE 'UTC', false);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58410
f
landsraad_has_term_of_task_ended
in_task_id bigint
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_has_term_of_task_ended(in_task_id bigint)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
DECLARE
	term_ended BOOLEAN = FALSE;
BEGIN
    SELECT NOW() > term.end_time FROM landsraad_tasks AS task LEFT JOIN landsraad_decree_term AS term ON task.term_id = term.term_id WHERE task.id = in_task_id INTO term_ended;
    RETURN term_ended;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58411
f
landsraad_initialize_system
number_of_weeks_term_retention integer, number_of_nominated_decrees integer, in_end_time timestamp without time zone, in_test_term boolean, faction_names text[], decrees dune.landsraaddecree[], tasks dune.landsraadtask[], task_rewards dune.landsraadtaskreward[]
TABLE(term_id bigint, reigning_faction_name text, active_decree_name text, winning_faction_name text, elected_decree_name text, start_time timestamp without time zone, end_time timestamp without time zone)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_initialize_system(number_of_weeks_term_retention integer, number_of_nominated_decrees integer, in_end_time timestamp without time zone, in_test_term boolean, faction_names text[], decrees dune.landsraaddecree[], tasks dune.landsraadtask[], task_rewards dune.landsraadtaskreward[])
 RETURNS TABLE(term_id bigint, reigning_faction_name text, active_decree_name text, winning_faction_name text, elected_decree_name text, start_time timestamp without time zone, end_time timestamp without time zone)
 LANGUAGE plpgsql
AS $function$
DECLARE
	current_term_id BIGINT = NULL;
BEGIN
	LOCK TABLE landsraad_decree_term, landsraad_decree_rotation, landsraad_decrees, landsraad_decree_votes IN EXCLUSIVE MODE;

	CALL landsraad_update_decrees(decrees);
	
	CALL landsraad_update_factions(faction_names);

	SELECT term.term_id FROM landsraad_decree_term term ORDER BY term.term_id DESC LIMIT 1 INTO current_term_id;
	IF FOUND THEN
		RETURN query SELECT term.term_id, reigning_faction.name, active_decree.decree_name, winning_faction.name, elected_decree.decree_name, (term.start_time AT TIME ZONE 'UTC')::TIMESTAMP, (term.end_time AT TIME ZONE 'UTC')::TIMESTAMP
			FROM landsraad_decree_term term 
			LEFT JOIN factions reigning_faction ON term.reigning_faction_id = reigning_faction.id 
			LEFT JOIN landsraad_decrees active_decree ON term.active_decree_id = active_decree.id
			LEFT JOIN factions winning_faction ON term.winning_faction_id = winning_faction.id
			LEFT JOIN landsraad_decrees elected_decree ON term.elected_decree_id = elected_decree.id
			WHERE term.term_id = current_term_id;
	ELSE 
		RETURN query SELECT term.term_id, term.reigning_faction_name, term.active_decree_name, term.winning_faction_name, term.elected_decree_name, term.start_time, term.end_time
			FROM landsraad_initialize_term(number_of_weeks_term_retention, number_of_nominated_decrees, in_end_time, in_test_term, tasks, task_rewards) AS term;	
	END IF;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58412
f
landsraad_initialize_term
number_of_weeks_term_retention integer, number_of_nominated_decrees integer, in_end_time timestamp without time zone, in_test_term boolean, tasks dune.landsraadtask[], task_rewards dune.landsraadtaskreward[]
TABLE(term_id bigint, reigning_faction_name text, active_decree_name text, winning_faction_name text, elected_decree_name text, start_time timestamp without time zone, end_time timestamp without time zone)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_initialize_term(number_of_weeks_term_retention integer, number_of_nominated_decrees integer, in_end_time timestamp without time zone, in_test_term boolean, tasks dune.landsraadtask[], task_rewards dune.landsraadtaskreward[])
 RETURNS TABLE(term_id bigint, reigning_faction_name text, active_decree_name text, winning_faction_name text, elected_decree_name text, start_time timestamp without time zone, end_time timestamp without time zone)
 LANGUAGE plpgsql
AS $function$
DECLARE
	reigning_faction_id SMALLINT = NULL;
	active_decree_id BIGINT = NULL;
	last_active_decree_id BIGINT = NULL;
	current_term_id BIGINT = NULL;
BEGIN
	LOCK TABLE landsraad_decrees, landsraad_decree_term, landsraad_decree_rotation, landsraad_decree_votes IN EXCLUSIVE MODE;

	-- read winning faction, elected and active decree from previous term
	SELECT term.winning_faction_id, term.elected_decree_id, term.active_decree_id INTO reigning_faction_id, active_decree_id, last_active_decree_id FROM landsraad_decree_term term ORDER BY term.term_id DESC LIMIT 1;

	-- insert new term
	INSERT INTO landsraad_decree_term (reigning_faction_id, active_decree_id, start_time, end_time, test_term) VALUES(reigning_faction_id, active_decree_id, now(), in_end_time AT TIME ZONE 'UTC', in_test_term) RETURNING landsraad_decree_term.term_id INTO current_term_id;

	-- cleanup old terms, except for previous one
	DELETE FROM landsraad_decree_term term WHERE term.end_time < (now() - MAKE_INTERVAL(weeks => number_of_weeks_term_retention)) AND term.term_id < current_term_id - 1;

	-- insert tasks for new term
	CALL landsraad_insert_tasks(current_term_id, tasks, task_rewards);

	-- insert decrees for voting
	CALL landsraad_nominate_decrees_for_voting(last_active_decree_id, number_of_nominated_decrees);

	-- clean expired landsraad contracts
	PERFORM journey_story_node_cooldown_delete_expired(now() at time zone 'utc');

	RETURN query SELECT term.term_id, reigning_faction.name, active_decree.decree_name, winning_faction.name, elected_decree.decree_name, (term.start_time AT TIME ZONE 'UTC')::TIMESTAMP, (term.end_time AT TIME ZONE 'UTC')::TIMESTAMP
		FROM landsraad_decree_term term
		LEFT JOIN factions reigning_faction ON term.reigning_faction_id = reigning_faction.id 
		LEFT JOIN landsraad_decrees active_decree ON term.active_decree_id = active_decree.id
		LEFT JOIN factions winning_faction ON term.winning_faction_id = winning_faction.id
		LEFT JOIN landsraad_decrees elected_decree ON term.elected_decree_id = elected_decree.id
		WHERE term.term_id = current_term_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58413
f
landsraad_insert_task_progress
in_term_id bigint, in_player_id bigint, in_guild_id bigint, in_house_name text, in_faction_progress integer, in_guild_progress real, in_player_progress real, in_timestamp timestamp without time zone
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_insert_task_progress(in_term_id bigint, in_player_id bigint, in_guild_id bigint, in_house_name text, in_faction_progress integer, in_guild_progress real, in_player_progress real, in_timestamp timestamp without time zone)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	guild_id BIGINT = NULL;
	faction_id BIGINT = NULL;
	faction_name TEXT = NULL;
	progress_id BIGINT = NULL;
BEGIN

	IF in_player_id IS NULL THEN
		-- use guild_id
		SELECT guilds.guild_id, factions.id, factions.name FROM guild_members 
			INNER JOIN guilds ON guild_members.guild_id = guilds.guild_id 
			LEFT JOIN factions ON guilds.guild_faction = factions.id
			WHERE guild_members.guild_id = in_guild_id INTO guild_id, faction_id, faction_name;
	ELSE
		-- use player_id
		SELECT guilds.guild_id, factions.id, factions.name FROM guild_members 
			INNER JOIN guilds ON guild_members.guild_id = guilds.guild_id 
			LEFT JOIN factions ON guilds.guild_faction = factions.id
			WHERE guild_members.player_id = in_player_id INTO guild_id, faction_id, faction_name;
	END IF;

	IF guild_id IS NULL THEN
		RAISE EXCEPTION 'Cannot insert landsraad task progress, player % not in guild', in_player_id;
	END IF;

	IF faction_id IS NULL OR faction_name = 'None' THEN 
		RAISE EXCEPTION 'Cannot insert landsraad task progress, guild % not aligned to faction', guild_id;
	END IF;

	INSERT INTO landsraad_task_progress (faction_id, task_id, faction_progress, guild_progress, player_progress, timestamp) 
		SELECT faction_id, tasks.id, in_faction_progress, in_guild_progress, in_player_progress, in_timestamp AT TIME ZONE 'UTC'
		FROM landsraad_tasks AS tasks 
		WHERE tasks.term_id = in_term_id AND tasks.house_name = in_house_name
		RETURNING id INTO progress_id;

	IF in_player_id IS NOT NULL THEN
		INSERT INTO landsraad_task_progress_player (progress_id, player_id) VALUES (progress_id, in_player_id);
	END IF;

	INSERT INTO landsraad_task_progress_guild (progress_id, guild_id) VALUES (progress_id, guild_id);

END $function$

@@@ENDRTN@@@
@@@RTN@@@58414
f
landsraad_insert_task_progress_batched
in_term_id bigint, in_task_progress dune.landsraadtaskprogress[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_insert_task_progress_batched(in_term_id bigint, in_task_progress dune.landsraadtaskprogress[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	task_progress record = NULL;
BEGIN
	FOREACH task_progress IN ARRAY in_task_progress
	LOOP
		PERFORM landsraad_insert_task_progress(in_term_id, task_progress.player_id, task_progress.guild_id ,task_progress.house_name, task_progress.faction_progress, task_progress.guild_progress, task_progress.player_progress, task_progress.timestamp);
	END LOOP;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58415
f
landsraad_insert_task_progress_faction
in_term_id bigint, in_faction_name text, in_house_name text, in_faction_progress integer, in_guild_progress real, in_player_progress real
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_insert_task_progress_faction(in_term_id bigint, in_faction_name text, in_house_name text, in_faction_progress integer, in_guild_progress real, in_player_progress real)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	player_id BIGINT = NULL;
	guild_id BIGINT = NULL;
BEGIN
	SELECT guild_members.player_id, guilds.guild_id FROM guilds 
		LEFT JOIN factions ON guilds.guild_faction = factions.id
		RIGHT JOIN guild_members ON guild_members.guild_id = guilds.guild_id
		WHERE factions.name = in_faction_name
		ORDER BY random() LIMIT 1 INTO player_id, guild_id;
	
	IF player_id IS NULL THEN 
		RAISE EXCEPTION 'Cannot insert landsraad task progress for faction %, no guild member found', in_faction_name;
	END IF;
	
	PERFORM landsraad_insert_task_progress(in_term_id, player_id, guild_id, in_house_name, in_faction_progress, in_guild_progress, in_player_progress, now() AT TIME ZONE 'UTC');
END $function$

@@@ENDRTN@@@
@@@RTN@@@58416
f
landsraad_insert_task_progress_random
in_term_id bigint, in_faction_names text[], in_num_rows integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_insert_task_progress_random(in_term_id bigint, in_faction_names text[], in_num_rows integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	player_id BIGINT = NULL;
	guild_id BIGINT = NULL;
	house_name TEXT = NULL;
	random_amount INTEGER = NULL;
BEGIN
	FOR r IN 1..in_num_rows
	LOOP
		SELECT guild_members.player_id, guilds.guild_id FROM guilds 
			LEFT JOIN factions ON guilds.guild_faction = factions.id
			RIGHT JOIN guild_members ON guild_members.guild_id = guilds.guild_id
			WHERE factions.name = ANY(in_faction_names)
			ORDER BY random() LIMIT 1 INTO player_id, guild_id;
		SELECT tasks.house_name FROM landsraad_tasks tasks
			WHERE tasks.term_id = in_term_id 
			ORDER BY random() LIMIT 1 INTO house_name;
		SELECT (floor(random() * 5) + 1)::INTEGER * 10 INTO random_amount;
		IF player_id IS NOT NULL AND house_name IS NOT NULL THEN
			PERFORM landsraad_insert_task_progress(in_term_id, player_id, guild_id, house_name, random_amount * 10, (random_amount * 0.1)::REAL, (random_amount * 10)::REAL, now() AT TIME ZONE 'UTC');
		END IF;
	END LOOP;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58417
p
landsraad_insert_tasks
IN in_term_id bigint, IN in_tasks dune.landsraadtask[], IN in_task_rewards dune.landsraadtaskreward[]


@@@BODY@@@
CREATE OR REPLACE PROCEDURE dune.landsraad_insert_tasks(IN in_term_id bigint, IN in_tasks dune.landsraadtask[], IN in_task_rewards dune.landsraadtaskreward[])
 LANGUAGE plpgsql
AS $procedure$
BEGIN
	INSERT INTO landsraad_tasks (term_id, board_index, house_name, goal_amount)
		SELECT in_term_id, tasks.board_index, tasks.house_name, tasks.goal_amount FROM UNNEST(in_tasks) AS tasks;
		
	INSERT INTO landsraad_task_rewards (task_id, threshold, template_id, amount)
		SELECT tasks.id, task_rewards.threshold, task_rewards.template_id, task_rewards.amount FROM UNNEST(in_task_rewards) AS task_rewards
		LEFT JOIN landsraad_tasks AS tasks ON task_rewards.house_name = tasks.house_name WHERE tasks.term_id = in_term_id;
END $procedure$

@@@ENDRTN@@@
@@@RTN@@@58418
f
landsraad_load_current_rotation
in_term_id bigint
TABLE(decree_name text, received_votes integer, open_votes integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_load_current_rotation(in_term_id bigint)
 RETURNS TABLE(decree_name text, received_votes integer, open_votes integer)
 LANGUAGE plpgsql
AS $function$
DECLARE
	open_votes INTEGER = 0;
BEGIN
	WITH open_guild_votes AS (
		SELECT guild_contribution.guild_id, FLOOR(SUM(guild_contribution.amount))::INTEGER AS voting_influence
			FROM landsraad_tasks AS tasks 
			INNER JOIN landsraad_task_guild_contributions AS guild_contribution
			ON guild_contribution.task_id = tasks.id AND guild_contribution.faction_id = tasks.winning_faction_id
			LEFT JOIN landsraad_decree_votes 
			ON guild_contribution.guild_id = landsraad_decree_votes.guild_id 
			WHERE tasks.term_id = in_term_id AND tasks.winning_faction_id = (SELECT winning_faction_id FROM landsraad_decree_term WHERE term_id = in_term_id)
			AND landsraad_decree_votes.guild_id IS NULL
			GROUP BY (guild_contribution.guild_id, guild_contribution.faction_id)
	) 
	SELECT SUM(voting_influence)::INTEGER FROM open_guild_votes INTO open_votes;

	RETURN query (
		SELECT decrees.decree_name, SUM(decree_votes.influence)::INTEGER AS received_votes, open_votes
			FROM landsraad_decree_rotation AS rotation
			INNER JOIN landsraad_decrees AS decrees ON rotation.decree_id = decrees.id
			LEFT JOIN landsraad_decree_votes AS decree_votes ON decree_votes.decree_id = rotation.decree_id
			GROUP BY rotation.decree_id, decrees.decree_name
	);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58419
f
landsraad_load_current_term

TABLE(term_id bigint, reigning_faction_name text, active_decree_name text, winning_faction_name text, elected_decree_name text, start_time timestamp without time zone, end_time timestamp without time zone, tasks dune.landsraadtask[], term_task_rewards dune.landsraadtaskreward[], winner_history text[], testterm boolean)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_load_current_term()
 RETURNS TABLE(term_id bigint, reigning_faction_name text, active_decree_name text, winning_faction_name text, elected_decree_name text, start_time timestamp without time zone, end_time timestamp without time zone, tasks dune.landsraadtask[], term_task_rewards dune.landsraadtaskreward[], winner_history text[], testterm boolean)
 LANGUAGE plpgsql
AS $function$
DECLARE
	current_term_id BIGINT = NULL;
	reigning_faction_name TEXT = NULL;
	active_decree_name TEXT = NULL;
	winning_faction_name TEXT = NULL;
	elected_decree_name TEXT = NULL;
	start_time TIMESTAMP = NULL;
	end_time TIMESTAMP = NULL;
	test_term BOOL = NULL;
	term_tasks LandsraadTask[];
	term_task_rewards LandsraadTaskReward[];
	term_winner_history TEXT[];
BEGIN
	SELECT term.term_id, reigning_faction.name, active_decree.decree_name, winning_faction.name, elected_decree.decree_name, (term.start_time AT TIME ZONE 'UTC')::TIMESTAMP, (term.end_time AT TIME ZONE 'UTC')::TIMESTAMP, term.test_term
		INTO current_term_id, reigning_faction_name, active_decree_name, winning_faction_name, elected_decree_name, start_time, end_time, test_term
		FROM landsraad_decree_term AS term
		LEFT JOIN factions AS reigning_faction ON term.reigning_faction_id = reigning_faction.id 
		LEFT JOIN landsraad_decrees AS active_decree ON term.active_decree_id = active_decree.id
		LEFT JOIN factions AS winning_faction ON term.winning_faction_id = winning_faction.id
		LEFT JOIN landsraad_decrees AS elected_decree ON term.elected_decree_id = elected_decree.id
		ORDER BY term.start_time DESC LIMIT 1;

	SELECT ARRAY_AGG((tasks.board_index, tasks.house_name, tasks.completed, COALESCE(factions_winner.name, ''), tasks.sysselraad, tasks.goal_amount)::LandsraadTask)
        INTO term_tasks		
        FROM landsraad_tasks AS tasks 
		LEFT JOIN factions AS factions_winner ON tasks.winning_faction_id = factions_winner.id
		WHERE tasks.term_id = current_term_id;

	WITH task_rewards AS (
		SELECT tasks.house_name AS house_name, rewards.threshold AS threshold, rewards.template_id AS template_id, rewards.amount AS amount 
		FROM landsraad_task_rewards AS rewards
		LEFT JOIN landsraad_tasks AS tasks ON rewards.task_id = tasks.id
		WHERE tasks.term_id = current_term_id ORDER BY rewards.task_id ASC, rewards.threshold ASC
	)
	SELECT ARRAY_AGG((house_name, threshold, template_id, amount)::LandsraadTaskReward) FROM task_rewards INTO term_task_rewards;

	SELECT ARRAY_AGG(winning_factions.name::TEXT)
	INTO term_winner_history
	FROM
		(SELECT factions.name
			FROM landsraad_decree_term
			LEFT JOIN factions ON landsraad_decree_term.reigning_faction_id = factions.id
			ORDER BY landsraad_decree_term.term_id DESC) AS winning_factions;

	IF current_term_id IS NOT NULL THEN
		RETURN query SELECT current_term_id, reigning_faction_name, active_decree_name, winning_faction_name, elected_decree_name, start_time, end_time, term_tasks, term_task_rewards, term_winner_history, test_term;
	END IF;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58420
f
landsraad_load_guild_contribution
in_term_id bigint, in_guild_id bigint, in_faction_id bigint
TABLE(voting_influence real)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_load_guild_contribution(in_term_id bigint, in_guild_id bigint, in_faction_id bigint)
 RETURNS TABLE(voting_influence real)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN query (
		SELECT SUM(guild_contribution.amount)::REAL
			FROM landsraad_tasks AS tasks 
			INNER JOIN landsraad_task_guild_contributions AS guild_contribution
			ON guild_contribution.task_id = tasks.id
			WHERE tasks.term_id = in_term_id AND guild_contribution.guild_id = in_guild_id AND guild_contribution.faction_id = in_faction_id
			GROUP BY (guild_contribution.guild_id, guild_contribution.faction_id)
	);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58421
f
landsraad_load_guild_contributions
in_term_id bigint, in_num_guilds integer, in_faction_names text[]
TABLE(faction_name text, guild_name text, voting_influence real)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_load_guild_contributions(in_term_id bigint, in_num_guilds integer, in_faction_names text[])
 RETURNS TABLE(faction_name text, guild_name text, voting_influence real)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN query (
		SELECT factions.name, top_guilds.guild_name, top_guilds.influence FROM factions
		CROSS JOIN LATERAL (
			SELECT guilds.guild_name as guild_name, SUM(guild_contribution.amount)::REAL AS influence
			FROM landsraad_tasks AS tasks 
			INNER JOIN landsraad_task_guild_contributions AS guild_contribution
			ON guild_contribution.task_id = tasks.id AND guild_contribution.faction_id = factions.id
			JOIN guilds
			ON guild_contribution.guild_id = guilds.guild_id
			WHERE tasks.term_id = in_term_id
			GROUP BY (guilds.guild_id, guilds.guild_name)
			ORDER BY influence DESC LIMIT in_num_guilds
		) AS top_guilds
		WHERE factions.name = ANY(in_faction_names)	
	);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58422
f
landsraad_load_guild_vote
in_term_id bigint, in_player_id bigint
TABLE(decree_name text, voting_influence real)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_load_guild_vote(in_term_id bigint, in_player_id bigint)
 RETURNS TABLE(decree_name text, voting_influence real)
 LANGUAGE plpgsql
AS $function$
DECLARE
	term_winning_faction_id SMALLINT = NULL;
	player_guild_id BIGINT = NULL;
	guild_faction_id SMALLINT = NULL;
BEGIN
	SELECT guilds.guild_id, guilds.guild_faction FROM guild_members JOIN guilds ON guild_members.guild_id = guilds.guild_id WHERE guild_members.player_id = in_player_id INTO player_guild_id, guild_faction_id;

	RETURN query (
		SELECT 
			CASE WHEN player_guild_id IS NOT NULL AND guild_faction_id IS NOT NULL THEN
				(SELECT COALESCE(decrees.decree_name, '') FROM landsraad_decree_votes AS votes LEFT JOIN landsraad_decrees AS decrees ON votes.decree_id = decrees.id WHERE votes.guild_id = player_guild_id)
			ELSE
				''
			END,
			CASE WHEN player_guild_id IS NOT NULL AND guild_faction_id IS NOT NULL THEN
				(SELECT landsraad_load_guild_contribution(in_term_id, player_guild_id, guild_faction_id))
			ELSE
				0
			END
	);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58423
f
landsraad_load_house_rewards
in_player_id bigint
TABLE(house_name text, template_id text, amount integer, last_updated timestamp without time zone)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_load_house_rewards(in_player_id bigint)
 RETURNS TABLE(house_name text, template_id text, amount integer, last_updated timestamp without time zone)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN query (SELECT rewards.house_name, rewards.template_id, rewards.amount, (rewards.last_updated AT TIME ZONE 'UTC')::TIMESTAMP FROM landsraad_house_rewards AS rewards WHERE player_id = in_player_id AND rewards.amount > 0);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58424
f
landsraad_load_player_contributions
in_term_id bigint, in_player_ids bigint[]
TABLE(player_id bigint, board_index smallint, amount integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_load_player_contributions(in_term_id bigint, in_player_ids bigint[])
 RETURNS TABLE(player_id bigint, board_index smallint, amount integer)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN query (
		SELECT contributions.player_id, tasks.board_index, FLOOR(SUM(contributions.amount))::INTEGER FROM landsraad_task_player_contributions AS contributions
			INNER JOIN landsraad_tasks AS tasks ON contributions.task_id = tasks.id
			WHERE tasks.term_id = in_term_id AND contributions.player_id = ANY(in_player_ids)
			GROUP BY contributions.player_id, tasks.board_index
	);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58425
f
landsraad_load_task_faction_progress
in_term_id bigint
TABLE(task_board_index integer, faction_name text, progress integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_load_task_faction_progress(in_term_id bigint)
 RETURNS TABLE(task_board_index integer, faction_name text, progress integer)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN query SELECT CAST(faction_progress.board_index AS INTEGER), faction_progress.name, CAST(faction_progress.progress AS INTEGER) FROM 
		(SELECT tasks.id, tasks.board_index, factions.name, SUM(faction_contribution.amount) AS progress
			FROM landsraad_tasks tasks 
			INNER JOIN landsraad_task_faction_contributions faction_contribution
			ON faction_contribution.task_id = tasks.id
			LEFT JOIN factions factions
			ON factions.id = faction_contribution.faction_id
			WHERE tasks.term_id = in_term_id
			GROUP BY (tasks.id, tasks.board_index, factions.name)) AS faction_progress;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58426
f
landsraad_load_task_faction_reveal_state
in_term_id bigint
TABLE(task_board_index integer, faction_name text, reveal_state boolean, time_stamp timestamp without time zone)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_load_task_faction_reveal_state(in_term_id bigint)
 RETURNS TABLE(task_board_index integer, faction_name text, reveal_state boolean, time_stamp timestamp without time zone)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN query SELECT CAST(tasks.board_index AS INTEGER), factions.name, reveal_state.revealed, (reveal_state.timestamp AT TIME ZONE 'UTC')::TIMESTAMP
			FROM landsraad_tasks tasks 
			INNER JOIN landsraad_task_reveal_state reveal_state
			ON reveal_state.task_id = tasks.id
			INNER JOIN factions factions
			ON factions.id = reveal_state.faction_id
			WHERE tasks.term_id = in_term_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58427
f
landsraad_load_term_progress
in_term_id bigint, in_num_guilds integer, in_faction_names text[], in_player_ids bigint[]
TABLE(faction_progress dune.landsraadtaskfactionprogress[], faction_reveal_state dune.landsraadtaskfactionrevealstate[], guild_contributions dune.landsraadguildcontribution[], player_contributions dune.landsraadplayercontribution[])

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_load_term_progress(in_term_id bigint, in_num_guilds integer, in_faction_names text[], in_player_ids bigint[])
 RETURNS TABLE(faction_progress dune.landsraadtaskfactionprogress[], faction_reveal_state dune.landsraadtaskfactionrevealstate[], guild_contributions dune.landsraadguildcontribution[], player_contributions dune.landsraadplayercontribution[])
 LANGUAGE plpgsql
AS $function$
DECLARE
	term_faction_progress LandsraadTaskFactionProgress[];
	term_faction_reveal_state LandsraadTaskFactionRevealState[];
	term_guild_contributions LandsraadGuildContribution[];
	term_player_contributions LandsraadPlayerContribution[];
BEGIN
	SELECT ARRAY_AGG((task_board_index, faction_name, progress)::LandsraadTaskFactionProgress) FROM landsraad_load_task_faction_progress(in_term_id) INTO term_faction_progress;
	SELECT ARRAY_AGG((task_board_index, faction_name, reveal_state, time_stamp)::LandsraadTaskFactionRevealState) FROM landsraad_load_task_faction_reveal_state(in_term_id) INTO term_faction_reveal_state;
	SELECT ARRAY_AGG((faction_name, guild_name, voting_influence)::LandsraadGuildContribution) FROM landsraad_load_guild_contributions(in_term_id, in_num_guilds, in_faction_names) INTO term_guild_contributions;
	SELECT ARRAY_AGG((player_id, board_index, amount)::LandsraadPlayerContribution) FROM landsraad_load_player_contributions(in_term_id, in_player_ids) INTO term_player_contributions;

	RETURN query SELECT term_faction_progress, term_faction_reveal_state, term_guild_contributions, term_player_contributions;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58428
p
landsraad_nominate_decrees_for_voting
IN last_active_decree_id bigint, IN num_decrees integer


@@@BODY@@@
CREATE OR REPLACE PROCEDURE dune.landsraad_nominate_decrees_for_voting(IN last_active_decree_id bigint, IN num_decrees integer)
 LANGUAGE plpgsql
AS $procedure$
BEGIN
	LOCK TABLE landsraad_decrees, landsraad_decree_rotation, landsraad_decree_votes IN EXCLUSIVE MODE;
	
	TRUNCATE TABLE landsraad_decree_votes;
	TRUNCATE TABLE landsraad_decree_rotation;

	INSERT INTO landsraad_decree_rotation
		SELECT id FROM landsraad_decrees
		WHERE (
			CASE WHEN last_active_decree_id IS NULL THEN
				True
			ELSE
				last_active_decree_id != id
			END
		) AND disabled = FALSE
		ORDER BY RANDOM() * weight DESC
		LIMIT num_decrees;
END $procedure$

@@@ENDRTN@@@
@@@RTN@@@58429
f
landsraad_notify_house_rewards_changed

trigger

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_notify_house_rewards_changed()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN
	PERFORM pg_notify('landsraad_notify_channel', format('house_rewards_changed#{"PlayerId" : %s}', NEW.player_id));
    RETURN NULL;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58430
f
landsraad_perform_daily_task_reveal
in_term_id bigint, in_faction_names text[], in_house_names_to_reveal text[], in_reveal_day integer
TABLE(faction_name text, house_name text, board_index integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_perform_daily_task_reveal(in_term_id bigint, in_faction_names text[], in_house_names_to_reveal text[], in_reveal_day integer)
 RETURNS TABLE(faction_name text, house_name text, board_index integer)
 LANGUAGE plpgsql
AS $function$
DECLARE
    last_processed_reveal_day INTEGER = NULL;
    faction_ids BIGINT[];
	newly_revealed_task_ids BIGINT[];
	newly_revealed_house_names TEXT[];
	newly_revealed_task_board_indices INTEGER[];
    faction_of_newly_revealed_task BIGINT[];
BEGIN
    LOCK TABLE landsraad_decree_term IN EXCLUSIVE MODE;
    
    SELECT landsraad_decree_term.last_processed_reveal_day FROM landsraad_decree_term WHERE term_id = in_term_id INTO last_processed_reveal_day;

    IF last_processed_reveal_day < in_reveal_day THEN
        SELECT ARRAY_AGG(factions.id) FROM factions WHERE factions.name = ANY(in_faction_names) INTO faction_ids;
                
        WITH revealed_task(id, faction_id) AS (
            SELECT task.id, faction.id FROM landsraad_tasks AS task
                CROSS JOIN UNNEST(faction_ids) AS faction(id)
                WHERE task.house_name = ANY (in_house_names_to_reveal) AND task.term_id = in_term_id)
        --filter out tasks already revealed from data to not stomp reveal date or send duplicate reveal event in telemetry
        SELECT ARRAY_AGG(task.id), ARRAY_AGG(task.house_name), ARRAY_AGG(task.board_index), ARRAY_AGG(revealed_task.faction_id) FROM revealed_task
            INNER JOIN landsraad_tasks AS task ON task.id = revealed_task.id
            LEFT JOIN landsraad_task_reveal_state AS reveal_state ON task.id = reveal_state.task_id AND revealed_task.faction_id = reveal_state.faction_id
            WHERE reveal_state.revealed IS NULL OR reveal_state.revealed IS FALSE
            INTO newly_revealed_task_ids, newly_revealed_house_names, newly_revealed_task_board_indices, faction_of_newly_revealed_task;
        
        INSERT INTO landsraad_task_reveal_state (task_id, faction_id, revealed, timestamp) SELECT UNNEST(newly_revealed_task_ids), UNNEST(faction_of_newly_revealed_task), TRUE, now() 
            ON CONFLICT(task_id, faction_id) DO UPDATE SET revealed = TRUE, timestamp = now();
        
        UPDATE landsraad_decree_term SET last_processed_reveal_day = in_reveal_day WHERE term_id = in_term_id;
        
        IF cardinality(newly_revealed_task_ids) > 0 THEN
            PERFORM pg_notify('landsraad_notify_channel', 'progress_updated#{"changed": true}');
        END IF;
    END IF;

	RETURN query
        WITH newly_revealed_tasks (house_name, board_index, faction_id) AS (
            SELECT UNNEST(newly_revealed_house_names), UNNEST(newly_revealed_task_board_indices), UNNEST(faction_of_newly_revealed_task))
        SELECT factions.name, newly_revealed_tasks.house_name, newly_revealed_tasks.board_index FROM newly_revealed_tasks JOIN factions ON newly_revealed_tasks.faction_id = factions.id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58431
f
landsraad_process_house_rewards

trigger

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_process_house_rewards()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN
	WITH 
		task_player_contribution_threshold_passed (player_id, house_name, template_id, amount) AS (
			SELECT NEW.player_id, tasks.house_name, task_rewards.template_id, task_rewards.amount
			FROM landsraad_task_rewards as task_rewards
            INNER JOIN landsraad_tasks AS tasks
            ON task_rewards.task_id = tasks.id
			LEFT JOIN landsraad_task_player_contributions AS player_contributions
			ON player_contributions.task_id = tasks.id
			WHERE task_rewards.task_id = NEW.task_id
				AND tasks.id = NEW.task_id 
				AND player_contributions.player_id = NEW.player_id
                AND COALESCE(OLD.amount, 0) < task_rewards.threshold
				AND NEW.amount >= task_rewards.threshold)
	INSERT INTO landsraad_house_rewards (player_id, house_name, template_id, amount, last_updated)
		SELECT player_id, house_name, template_id, SUM(amount), CURRENT_TIMESTAMP FROM task_player_contribution_threshold_passed GROUP BY player_id, house_name, template_id
		ON CONFLICT (player_id, house_name, template_id) DO UPDATE SET amount = landsraad_house_rewards.amount + excluded.amount, last_updated = CURRENT_TIMESTAMP;

	RETURN NULL;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58432
f
landsraad_process_task_progress
max_rows integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_process_task_progress(max_rows integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	task_progress RECORD = NULL;
	current_term_id BIGINT = NULL;
	last_progress_id BIGINT = NULL;
	old_processed_id BIGINT = NULL;
	new_processed_id BIGINT = NULL;
	new_amount INTEGER = 0;
	player BIGINT = NULL;
	guild BIGINT = NULL;
	notify_guild_ids BIGINT[];
	guild_ids_json JSON = NULL;
BEGIN
	LOCK TABLE landsraad_task_progress_processed IN EXCLUSIVE MODE;

	SELECT term_id FROM landsraad_decree_term ORDER BY start_time DESC LIMIT 1 INTO current_term_id;

	SELECT id FROM landsraad_task_progress ORDER BY id DESC LIMIT 1 INTO last_progress_id;

	SELECT last_processed_id FROM landsraad_task_progress_processed INTO old_processed_id;

	-- read batch of rows sorted by id, process ordered by timestamp
	FOR task_progress IN 
	WITH progress_batch AS (
		SELECT landsraad_task_progress.id, landsraad_task_progress.faction_id, landsraad_task_progress.task_id,
			landsraad_task_progress.faction_progress, landsraad_task_progress.guild_progress, landsraad_task_progress.player_progress, landsraad_task_progress.timestamp, 
			task_progress_players.players, task_progress_guilds.guilds
		FROM landsraad_task_progress,
		LATERAL (SELECT ARRAY_AGG(player_id) AS players FROM landsraad_task_progress_player WHERE landsraad_task_progress_player.progress_id = landsraad_task_progress.id) AS task_progress_players, 
		LATERAL (SELECT ARRAY_AGG(guild_id) AS guilds FROM landsraad_task_progress_guild WHERE landsraad_task_progress_guild.progress_id = landsraad_task_progress.id) AS task_progress_guilds
		WHERE CASE WHEN old_processed_id IS NOT NULL THEN id > old_processed_id ELSE TRUE END
		ORDER BY id LIMIT MAX_ROWS
	)
	SELECT id, faction_id, task_id, players, guilds, faction_progress, guild_progress, player_progress FROM progress_batch ORDER BY timestamp
	LOOP
		IF NOT (SELECT landsraad_has_term_of_task_ended(task_progress.task_id)) THEN
            -- player progress is allowed to happen even if the task was already completed
			IF task_progress.players IS NOT NULL THEN
				FOREACH player IN ARRAY task_progress.players 
				LOOP
					INSERT INTO landsraad_task_player_contributions AS player_contribution (player_id, faction_id, task_id, amount) 
						VALUES (player, task_progress.faction_id, task_progress.task_id, task_progress.player_progress) 
						ON CONFLICT (player_id, faction_id, task_id)
						DO UPDATE SET amount = player_contribution.amount + task_progress.player_progress;
				END LOOP;
			END IF;
            
            IF NOT (SELECT landsraad_task_has_been_completed(task_progress.task_id)) THEN
                IF task_progress.guilds IS NOT NULL THEN
                    FOREACH guild IN ARRAY task_progress.guilds
                    LOOP
                        -- only insert to guild contribution if no vote has been placed
                        IF (SELECT NOT EXISTS (SELECT 1 FROM landsraad_decree_votes WHERE landsraad_decree_votes.guild_id = guild)) THEN
                            INSERT INTO landsraad_task_guild_contributions AS guild_contribution (guild_id, faction_id, task_id, amount) 
                            VALUES (guild, task_progress.faction_id, task_progress.task_id, task_progress.guild_progress) 
                            ON CONFLICT (guild_id, faction_id, task_id)
                            DO UPDATE SET amount = guild_contribution.amount + task_progress.guild_progress;
                            notify_guild_ids = notify_guild_ids || guild;	
                        END IF;
                        
                    END LOOP;
                END IF;

                INSERT INTO landsraad_task_faction_contributions AS faction_contribution (faction_id, task_id, amount) 
                    VALUES (task_progress.faction_id, task_progress.task_id, task_progress.faction_progress) 
                    ON CONFLICT (faction_id, task_id)
                    DO UPDATE SET amount = faction_contribution.amount + task_progress.faction_progress;
            END IF;
		END IF;

		new_processed_id = task_progress.id;
	END LOOP;
	
	IF new_processed_id IS NOT NULL THEN
		IF old_processed_id IS NULL THEN
			INSERT INTO landsraad_task_progress_processed (last_processed_id) VALUES (new_processed_id);
		ELSE
			UPDATE landsraad_task_progress_processed SET last_processed_id = new_processed_id;
		END IF;
	END IF;
	
	IF last_progress_id > new_processed_id THEN
		PERFORM pg_notify('landsraad_notify_channel', format('progress_pressure#{"UnprocessedCount": %s}', last_progress_id - new_processed_id));
	END IF;
	
	IF new_processed_id > old_processed_id THEN
		PERFORM pg_notify('landsraad_notify_channel', 'progress_updated#{"changed": true}');
	ELSE
		PERFORM pg_notify('landsraad_notify_channel', 'progress_updated#{"changed": false}');
	END IF;

    IF cardinality(notify_guild_ids) > 0 THEN
        SELECT json_agg(DISTINCT guild_id) FROM (SELECT unnest(notify_guild_ids) guild_id) guilds INTO guild_ids_json;
        PERFORM pg_notify('landsraad_notify_channel', format('guild_vote_changed#{"GuildIds": %s}', guild_ids_json));
    END IF;

END $function$

@@@ENDRTN@@@
@@@RTN@@@58433
f
landsraad_task_has_been_completed
in_task_id bigint
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_task_has_been_completed(in_task_id bigint)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
DECLARE
    task_completed BOOLEAN = FALSE;
BEGIN
    SELECT task.completed FROM landsraad_tasks AS task WHERE task.id = in_task_id INTO task_completed;
    RETURN task_completed;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58434
p
landsraad_update_decrees
IN in_decrees dune.landsraaddecree[]


@@@BODY@@@
CREATE OR REPLACE PROCEDURE dune.landsraad_update_decrees(IN in_decrees dune.landsraaddecree[])
 LANGUAGE plpgsql
AS $procedure$
BEGIN
	UPDATE landsraad_decrees SET disabled = TRUE WHERE decree_name NOT IN (
        SELECT(UNNEST(in_decrees)).decree_name
    );
	INSERT INTO landsraad_decrees (decree_name, version, disabled, weight) 
		SELECT decrees.decree_name, decrees.version, decrees.disabled, decrees.weight FROM UNNEST(in_decrees) AS decrees
		ON CONFLICT(decree_name) DO UPDATE SET version = excluded.version, disabled = excluded.disabled, weight = excluded.weight;
END $procedure$

@@@ENDRTN@@@
@@@RTN@@@58435
p
landsraad_update_factions
IN in_faction_names text[]


@@@BODY@@@
CREATE OR REPLACE PROCEDURE dune.landsraad_update_factions(IN in_faction_names text[])
 LANGUAGE plpgsql
AS $procedure$
BEGIN
	WITH new_factions AS (
        SELECT f FROM UNNEST(in_faction_names) f LEFT JOIN factions ON f = factions.name WHERE id IS NULL
    )
	INSERT INTO factions (name) SELECT * FROM new_factions;
END $procedure$

@@@ENDRTN@@@
@@@RTN@@@58436
f
landsraad_update_task_faction_reveal_state
in_term_id bigint, in_task_board_index integer, faction_name text, reveal_state boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_update_task_faction_reveal_state(in_term_id bigint, in_task_board_index integer, faction_name text, reveal_state boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	taskid BIGINT = NULL;
	factionid BIGINT = NULL;
BEGIN
	SELECT id FROM landsraad_tasks tasks WHERE tasks.board_index = in_task_board_index AND tasks.term_id = in_term_id INTO taskid;
	
	IF taskid IS NULL THEN 
		RAISE EXCEPTION 'Cannot update landsraad task reveal state, no task id for index % term %', in_task_board_index, in_term_id;
	END IF;
	
	SELECT id FROM factions WHERE factions.name = faction_name INTO factionid;
	
	IF factionid IS NULL OR faction_name = 'None' THEN 
		RAISE EXCEPTION 'Cannot update landsraad task reveal state, invalid faction (%)', faction_name;
	END IF;
	
	INSERT INTO landsraad_task_reveal_state (task_id, faction_id, revealed, timestamp) VALUES (taskid, factionid, reveal_state, now()) ON CONFLICT(task_id, faction_id) DO UPDATE
		SET revealed = reveal_state, timestamp = now();

	PERFORM pg_notify('landsraad_notify_channel', 'progress_updated#{"changed": true}');	
END $function$

@@@ENDRTN@@@
@@@RTN@@@58437
f
landsraad_withdraw_house_reward
in_player_id bigint, in_house_rewards dune.landsraadplayerhousereward[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.landsraad_withdraw_house_reward(in_player_id bigint, in_house_rewards dune.landsraadplayerhousereward[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	current_amount INTEGER = NULL;
	house_reward record = NULL;
    grouped_house_rewards LandsraadPlayerHouseReward[];
BEGIN
	LOCK TABLE landsraad_house_rewards IN EXCLUSIVE MODE;
    --group rewards to make sure multiple entries of the same item do not slip by amount verfication below
    WITH grouped_rewards AS (SELECT house_name, template_id, SUM(amount) as amount FROM UNNEST(in_house_rewards) GROUP BY house_name, template_id)
        SELECT ARRAY_AGG((grouped_rewards.house_name, grouped_rewards.template_id, grouped_rewards.amount)::LandsraadPlayerHouseReward) INTO grouped_house_rewards FROM grouped_rewards;
    
	FOREACH house_reward in ARRAY grouped_house_rewards
	LOOP
		SELECT lhr.amount INTO current_amount FROM landsraad_house_rewards AS lhr WHERE lhr.player_id = in_player_id AND lhr.house_name = house_reward.house_name AND lhr.template_id = house_reward.template_id;

		IF current_amount IS NULL OR current_amount < house_reward.amount THEN
			RAISE EXCEPTION 'Cannot withdraw house reward %s for player % and house %s', house_reward.template_id, in_player_id, house_reward.house_name;
			RETURN;
		END IF;
	END LOOP;
	-- finish full loop of checks first, all rewards need to be withdrawable before updating
	FOREACH house_reward in ARRAY grouped_house_rewards
	LOOP
		UPDATE landsraad_house_rewards SET amount = amount - house_reward.amount, last_updated = CURRENT_TIMESTAMP WHERE player_id = in_player_id AND house_name = house_reward.house_name AND template_id = house_reward.template_id;
	END LOOP;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58438
f
load_actors
in_actor_ids bigint[], in_actor_state dune.actorstate
TABLE(ord bigint, actor_id bigint, generic_data dune.actorgenericdata, serial bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_actors(in_actor_ids bigint[], in_actor_state dune.actorstate DEFAULT 'Default'::dune.actorstate)
 RETURNS TABLE(ord bigint, actor_id bigint, generic_data dune.actorgenericdata, serial bigint)
 LANGUAGE plpgsql
AS $function$
begin
	return query
		with
			ids as (
				select * from unnest(in_actor_ids) with ordinality as t(id, ord)
			),
			entities as (
				select
					fgl_bridge.actor_id,
					(fgl_bridge.entity_id, fgl_bridge.slot_name, entity_data.components)::FglEntity as data
				from
					ids
					left join actor_fgl_entities as fgl_bridge on ids.id=fgl_bridge.actor_id
					left join fgl_entities as entity_data on fgl_bridge.entity_id = entity_data.entity_id
			)
		select
			ids.ord, actors.id,
			(
				coalesce(array_agg(entities.data) filter (where entities.data is not null), array[]::FglEntity[]),
				actors.properties,
				actors.gas_attributes,
				case
					when exists(select 1 from buildings where actors.id = buildings.id) then load_building(actors.id)
				end
				,
				case
					when exists(select 1 from placeables where actors.id = placeables.id) then load_placeable(actors.id)
				end
				,
				case
					when exists(select 1 from totems where actors.id = totems.id) then load_totem(actors.id)
				end
			)::ActorGenericData, actors.serial
		from
			ids
			join actors using (id)
			left join entities on actors.id = entities.actor_id
		where
        	case when (in_actor_state = 'Default') then
	        	not exists(select 1 from actor_state where actors.id = actor_state.actor_id)
    	    else
 	        	exists(select 1 from actor_state where actors.id = actor_state.actor_id and actor_state.state = in_actor_state)
    	    end
		group by ids.ord, actors.id, actors.properties, actors.gas_attributes, actors.serial
		order by ids.ord;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58439
f
load_backup_vehicle
in_account_id bigint
TABLE(out_id bigint, out_class text, out_customization_id text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_backup_vehicle(in_account_id bigint)
 RETURNS TABLE(out_id bigint, out_class text, out_customization_id text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
		SELECT a.id, a.class, bv.customization_id
		FROM actors a
		JOIN backup_vehicles bv on a.id = bv.vehicle_id 
		WHERE bv.account_id = in_account_id
		LIMIT 1;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58449
f
load_building
in_building_id bigint
dune.buildingsavedata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_building(in_building_id bigint)
 RETURNS dune.buildingsavedata
 LANGUAGE sql
BEGIN ATOMIC
 RETURN ( SELECT ROW(array_agg(ROW(building_instances.instance_id, building_instances.building_type, building_instances.transform, building_instances.owner_entity_id, building_instances.building_flags, building_instances.health, building_instances.shelter, building_instances.stabilization_begin_timespan, building_instances.stabilization_end_timespan, building_instances.stabilization_state, building_instances.sand_buildup)::dune.buildinginstance), ARRAY[]::integer[], ARRAY[]::dune.buildinginstanceupdateowner[], ARRAY[]::dune.buildinginstanceupdatestabilization[], ARRAY[]::dune.buildinginstanceupdatehealth[], ARRAY[]::dune.buildinginstanceupdateshelter[], ARRAY[]::dune.buildinginstanceupdatesandbuildup[], ARRAY[]::dune.buildinginstanceupdatebuildingflags[], ARRAY[]::dune.buildinginstanceupdatetransform[])::dune.buildingsavedata AS "row"
            FROM dune.building_instances
           WHERE (building_instances.building_id = load_building.in_building_id));
END

@@@ENDRTN@@@
@@@RTN@@@58450
f
load_communinet_player_data
in_account_id bigint
TABLE(is_active boolean, selected_channel_name text, channel_name text, is_tuned boolean)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_communinet_player_data(in_account_id bigint)
 RETURNS TABLE(is_active boolean, selected_channel_name text, channel_name text, is_tuned boolean)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
	SELECT cp.is_active, cp.selected_channel_name, cpc.channel_name, cpc.is_tuned
	FROM communinet_player AS cp  JOIN communinet_player_channels as cpc
	ON cp.account_id = cpc.account_id
	WHERE cpc.account_id = in_account_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58451
f
load_dialogue_data
in_player_controller_id bigint, OUT met_npcs text[], OUT taken_nodes integer[]
record

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_dialogue_data(in_player_controller_id bigint, OUT met_npcs text[], OUT taken_nodes integer[])
 RETURNS record
 LANGUAGE plpgsql
AS $function$
BEGIN
    SELECT ARRAY_AGG(npc_name)
    INTO met_npcs
    FROM dialogue_met_npcs
    WHERE player_id = in_player_controller_id;

    SELECT ARRAY_AGG(node_id)
    INTO taken_nodes
    FROM dialogue_taken_nodes
    WHERE player_id = in_player_controller_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58452
f
load_dimension_index
in_map text, in_partition_id bigint
integer

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_dimension_index(in_map text, in_partition_id bigint)
 RETURNS integer
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN (SELECT dimension_index from world_partition where map = in_map and partition_id = in_partition_id limit 1);
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58453
f
load_events_log_data_from_player
in_actor_id bigint, in_limit_entries_num integer
TABLE(game_event_owner bigint, universe_time timestamp without time zone, map_name text, partition_id bigint, event_type integer, x_location double precision, y_location double precision, z_location double precision, custom_data jsonb)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_events_log_data_from_player(in_actor_id bigint, in_limit_entries_num integer)
 RETURNS TABLE(game_event_owner bigint, universe_time timestamp without time zone, map_name text, partition_id bigint, event_type integer, x_location double precision, y_location double precision, z_location double precision, custom_data jsonb)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	SELECT temp.actor_id, (temp.universe_time AT TIME ZONE 'UTC')::TIMESTAMP, temp.map, temp.partition_id, temp.event_type, temp.x, temp.y, temp.z, temp.custom_data 
	FROM (SELECT game_events.actor_id, game_events.universe_time, game_events.map, game_events.partition_id, game_events.event_type, game_events.x, game_events.y, game_events.z, game_events.custom_data FROM game_events WHERE game_events.actor_id = in_actor_id AND game_events.player_facing_event = true ORDER BY game_events.universe_time DESC LIMIT in_limit_entries_num) temp 
	ORDER BY temp.universe_time ASC;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58454
f
load_full_actors
in_ids bigint[]
SETOF dune.actordescription

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_full_actors(in_ids bigint[])
 RETURNS SETOF dune.actordescription
 LANGUAGE plpgsql
AS $function$
begin
	return query
		with
			ids as (
				select * from unnest(in_ids) with ordinality as t(id, ord)
			),
			entities as (
				select
					actor_id,
					(fgl_bridge.entity_id, fgl_bridge.slot_name, entity_data.components)::FglEntity as data
				from
					ids
					left join actor_fgl_entities as fgl_bridge on ids.id=fgl_bridge.actor_id
					left join fgl_entities as entity_data using (entity_id)
			)
		select
			id, "class", "transform", (
				coalesce(array_agg(entities.data) filter (where entities.data is not null), array[]::FglEntity[]),
				actors.properties,
				actors.gas_attributes,
				case
					when exists(select 1 from buildings where ids.id = buildings.id) then load_building(id)
					else null
				end,
				case
					when exists(select 1 from placeables where ids.id = placeables.id) then load_placeable(id)
					else null
				end
				,
				case
					when exists(select 1 from totems where id = totems.id) then load_totem(id)
				end
			)::ActorGenericData, actors.serial
		from
			ids
			join actors using (id)
			left join entities on id=entities.actor_id
		group by ids.ord, id, class, "transform", actors.properties, actors.gas_attributes, actors.serial
		order by ids.ord;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58455
f
load_item
in_item_id bigint
TABLE(item_id bigint, stack_size bigint, quality_level bigint, volume_override real, position_index bigint, template_id text, inventory_id bigint, is_new boolean, acquisition_time bigint, stats jsonb, sub_inventory_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_item(in_item_id bigint)
 RETURNS TABLE(item_id bigint, stack_size bigint, quality_level bigint, volume_override real, position_index bigint, template_id text, inventory_id bigint, is_new boolean, acquisition_time bigint, stats jsonb, sub_inventory_id bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
    SELECT item.id, item.stack_size, item.quality_level, item.volume_override, item.position_index, item.template_id, item.inventory_id, item.is_new, item.acquisition_time, item.stats, inventory.id
    FROM items item
    LEFT JOIN inventories inventory ON (inventory.item_id = item.id)
    WHERE item.id = in_item_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58456
f
load_items
in_inventory_id bigint
TABLE(item_id bigint, stack_size bigint, quality_level bigint, volume_override real, position_index bigint, template_id text, inventory_id bigint, is_new boolean, acquisition_time bigint, stats jsonb, sub_inventory_id bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_items(in_inventory_id bigint)
 RETURNS TABLE(item_id bigint, stack_size bigint, quality_level bigint, volume_override real, position_index bigint, template_id text, inventory_id bigint, is_new boolean, acquisition_time bigint, stats jsonb, sub_inventory_id bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH RECURSIVE items_cte AS (
    SELECT item.id, item.stack_size, item.quality_level, item.volume_override, item.position_index, item.template_id, item.inventory_id, item.is_new, item.acquisition_time, item.stats, inventory.id as sub_inventory_id
    FROM items item
    LEFT JOIN inventories inventory ON (inventory.item_id = item.id)
    WHERE item.inventory_id = in_inventory_id
    UNION ALL
    SELECT item.id, item.stack_size, item.quality_level, item.volume_override, item.position_index, item.template_id, item.inventory_id, item.is_new, item.acquisition_time, item.stats, inventory.id as sub_inventory_id
    FROM items item
    LEFT JOIN inventories inventory ON (inventory.item_id = item.id)
    JOIN items_cte ON item.inventory_id = items_cte.sub_inventory_id
    WHERE items_cte.sub_inventory_id IS NOT NULL
)
SELECT * FROM items_cte order by id asc;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58457
f
load_map_areas_entries
in_account_id bigint, in_map_name text
TABLE(account_id bigint, area_id smallint, time_discovered timestamp without time zone, time_first_entered timestamp without time zone, survey_point_marker_id bigint, items_surveyed_target jsonb, items_surveyed_progress jsonb, map_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_map_areas_entries(in_account_id bigint, in_map_name text)
 RETURNS TABLE(account_id bigint, area_id smallint, time_discovered timestamp without time zone, time_first_entered timestamp without time zone, survey_point_marker_id bigint, items_surveyed_target jsonb, items_surveyed_progress jsonb, map_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
        SELECT
            map_areas.account_id,
            map_areas.area_id,
            map_areas.time_discovered AT TIME ZONE 'UTC',
            map_areas.time_first_entered AT TIME ZONE 'UTC',
            map_areas.survey_point_marker_id,
            map_areas.items_surveyed_target,
            map_areas.items_surveyed_progress,
            map_areas.map_name
        from map_areas WHERE map_areas.account_id = in_account_id AND map_areas.map_name = in_map_name;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58458
f
load_markers
in_player_id bigint, in_dimension_id integer, in_map_name text
TABLE(out_marker_hash_id integer, out_marker_type text, out_x double precision, out_y double precision, out_z double precision, out_payload_type text, out_area_id smallint, out_area_radius real, out_long_range boolean, out_payload jsonb, out_discovery_level smallint, out_discovery_method smallint, out_player_payload jsonb)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_markers(in_player_id bigint, in_dimension_id integer, in_map_name text)
 RETURNS TABLE(out_marker_hash_id integer, out_marker_type text, out_x double precision, out_y double precision, out_z double precision, out_payload_type text, out_area_id smallint, out_area_radius real, out_long_range boolean, out_payload jsonb, out_discovery_level smallint, out_discovery_method smallint, out_player_payload jsonb)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
   		SELECT
			player_markers.marker_hash_id,
			(markers.marker).marker_type,
			(markers.marker).x,
			(markers.marker).y,
			(markers.marker).z,
			(markers.marker).payload_type,
			area_id,
			area_radius,
			long_range,
			markers.payload,
			discovery_level,
			discovery_method,
			player_markers.payload
		FROM map_names JOIN markers ON markers.map_name_id = map_names.map_name_id
			JOIN player_markers ON markers.marker_hash_id = player_markers.marker_hash_id
			AND markers.dimension_index = player_markers.dimension_index
			AND markers.map_name_id = player_markers.map_name_id
		WHERE player_markers.player_id = in_player_id
			AND (player_markers.dimension_index = in_dimension_id OR player_markers.dimension_index = -1)
			AND map_names.map_name = in_map_name;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58459
f
load_partition_definition_map

TABLE(out_partition_id bigint, out_server_id text, out_partition_definition jsonb, out_dimension_index integer, out_blocked boolean, out_label text, out_map text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_partition_definition_map()
 RETURNS TABLE(out_partition_id bigint, out_server_id text, out_partition_definition jsonb, out_dimension_index integer, out_blocked boolean, out_label text, out_map text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
	SELECT wp.partition_id, active_server_ids.server_id AS server_id, wp.partition_definition,
		   wp.dimension_index, wp.blocked, wp.label, wp.map
	FROM world_partition as wp
	LEFT JOIN active_server_ids
	ON active_server_ids.server_id = wp.server_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58460
f
load_placeable
in_placeable_id bigint
dune.placeablesavedata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_placeable(in_placeable_id bigint)
 RETURNS dune.placeablesavedata
 LANGUAGE plpgsql
AS $function$
DECLARE
	result PlaceableSaveData;
BEGIN
	SELECT
		owner_entity_id as in_owner_entity_id,
		health as in_health,
		building_type as in_building_type,
		has_hit_ground as in_has_hit_ground,
		has_buildable_support as in_has_buildable_support,
		is_hologram as in_is_hologram
	INTO result
	FROM placeables
	WHERE id = in_placeable_id;
	return result;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58461
f
load_recovered_vehicles
in_account_id bigint, in_restore_time_limit integer
TABLE(out_vehicle_id bigint, out_class text, out_name text, out_time_stored timestamp without time zone, out_chassis_durability real, out_customization_id text, out_migrated boolean)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_recovered_vehicles(in_account_id bigint, in_restore_time_limit integer)
 RETURNS TABLE(out_vehicle_id bigint, out_class text, out_name text, out_time_stored timestamp without time zone, out_chassis_durability real, out_customization_id text, out_migrated boolean)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY
		SELECT vehicle_id, class, vehicle_name, time_stored AT TIME ZONE 'UTC', chassis_durability, customization_id, migrated 
		FROM actors 
		JOIN recovered_vehicles on id = vehicle_id 
		WHERE account_id = in_account_id
		AND (migrated = TRUE OR time_stored > NOW() - in_restore_time_limit * INTERVAL '1 second')
		AND EXISTS (SELECT 1 FROM actor_state WHERE actor_state.actor_id = id AND actor_state.state = 'VehicleRecovery')
		ORDER BY time_stored DESC;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58462
f
load_static_encounter_name
in_map_name text, in_package_name text, in_actor_name text
TABLE(encounter_name text, waiting_for_reset boolean)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_static_encounter_name(in_map_name text, in_package_name text, in_actor_name text)
 RETURNS TABLE(encounter_name text, waiting_for_reset boolean)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY 
    SELECT t.encounter_name, t.waiting_for_reset
    FROM encounters_static as t
    WHERE t.map_name = in_map_name AND t.package_name = in_package_name AND t.actor_name = in_actor_name;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58463
f
load_takeoverable_user_ids

SETOF dune.takeovercharacterdatacomposite

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_takeoverable_user_ids()
 RETURNS SETOF dune.takeovercharacterdatacomposite
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
	SELECT acc.user, ps.character_name
	FROM accounts acc LEFT JOIN player_state ps ON acc.id=ps.account_id
	WHERE acc.takeoverable=true;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58464
f
load_totem
in_id bigint
dune.totemsavedata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_totem(in_id bigint)
 RETURNS dune.totemsavedata
 LANGUAGE plpgsql
AS $function$
DECLARE
	result TotemSaveData;
BEGIN
	SELECT
		landclaim_vertical_level,
		last_backup_timestamp,
		landclaim_original_global_location,
		landclaim_original_global_yaw_rotation
	INTO result
	FROM totems
	WHERE id = in_id;
	return result;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58465
f
load_travel_return_info
in_player_controller_id bigint
TABLE(map text, transform dune.transform)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_travel_return_info(in_player_controller_id bigint)
 RETURNS TABLE(map text, transform dune.transform)
 LANGUAGE plpgsql
AS $function$
begin
	return query
	select travel_return_info.map, travel_return_info.transform
	from travel_return_info
	where player_controller_id = in_player_controller_id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58466
f
load_travel_to_player_info
in_player_controller_id bigint
TABLE(map text, transform dune.transform, partition_id bigint, dimension_index integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_travel_to_player_info(in_player_controller_id bigint)
 RETURNS TABLE(map text, transform dune.transform, partition_id bigint, dimension_index integer)
 LANGUAGE plpgsql
AS $function$
begin
	return query
	select actors.map, actors.transform, actors.partition_id, actors.dimension_index
	from player_state
	join actors on player_state.player_pawn_id = actors.id
	where player_state.player_controller_id = in_player_controller_id
	and player_state.online_status = 'Online';
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58467
f
load_vehicle_modules
in_vehicle_id bigint
TABLE(module_id bigint, template_id text, stats jsonb)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_vehicle_modules(in_vehicle_id bigint)
 RETURNS TABLE(module_id bigint, template_id text, stats jsonb)
 LANGUAGE plpgsql
AS $function$
begin
    return query SELECT vm.id, vm.template_id, vm.stats FROM vehicle_modules vm WHERE vehicle_id = in_vehicle_id ORDER BY id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58468
f
load_world_partition
in_map_name text, in_server_id text, in_desired_dimension_index bigint, in_desired_partition_id bigint
TABLE(partition_id bigint, partition_definition jsonb, dimension_index integer, blocked boolean, label text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.load_world_partition(in_map_name text, in_server_id text, in_desired_dimension_index bigint DEFAULT 0, in_desired_partition_id bigint DEFAULT NULL::bigint)
 RETURNS TABLE(partition_id bigint, partition_definition jsonb, dimension_index integer, blocked boolean, label text)
 LANGUAGE plpgsql
AS $function$
DECLARE
	tmp_partition RECORD;
BEGIN
	-- First check if the server already have a partition assigned
	SELECT INTO tmp_partition wp.partition_id, wp.partition_definition, wp.dimension_index, wp.blocked, wp.label
		FROM world_partition wp
		WHERE server_id = in_server_id AND wp.map = in_map_name AND wp.dimension_index = in_desired_dimension_index;
	IF tmp_partition.partition_id IS NOT NULL THEN
		RETURN QUERY SELECT tmp_partition.partition_id, tmp_partition.partition_definition, tmp_partition.dimension_index, tmp_partition.blocked, tmp_partition.label;
		RETURN;
	END IF;

	-- No partition assigned, so try to find an unassigned partition for this server
	SELECT INTO tmp_partition wp.partition_id, wp.partition_definition, wp.dimension_index, wp.blocked, wp.label
		FROM world_partition wp
		WHERE (server_id IS NULL OR server_id NOT IN (SELECT * FROM active_server_ids)) AND wp.map = in_map_name AND wp.dimension_index = in_desired_dimension_index
		ORDER BY (wp.partition_id = in_desired_partition_id) DESC, wp.partition_definition->'type', wp.partition_definition->'index', wp.partition_definition->'box'->'min_x', wp.partition_definition->'box'->'min_y'
		LIMIT 1
		FOR UPDATE SKIP LOCKED;
	IF tmp_partition.partition_id IS NULL THEN
		RETURN;
	ELSE
		-- Fake a server
		INSERT INTO farm_state(server_id, farm_id, outgoing_s2s_connections, incoming_s2s_connections, connected_players, igw_addr, igw_port, game_addr, game_port, map, revision)
			VALUES (in_server_id, '0', 0, 0, 0, '0.0.0.0', 0, '0.0.0.0', 0, '', 0) ON CONFLICT DO NOTHING;
		UPDATE world_partition SET server_id = in_server_id WHERE world_partition.partition_id = tmp_partition.partition_id;
		NOTIFY world_partition_update;
		RETURN QUERY SELECT tmp_partition.partition_id, tmp_partition.partition_definition, tmp_partition.dimension_index, tmp_partition.blocked, tmp_partition.label;
		RETURN;
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58469
f
log_cheating
in_fls_id text, in_cheat_type dune.cheat_type_enum, in_event_time timestamp with time zone
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.log_cheating(in_fls_id text, in_cheat_type dune.cheat_type_enum, in_event_time timestamp with time zone DEFAULT now())
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN

    -- Insert into suspicious_be
    INSERT INTO cheater_tracking (
        event_time,
        fls_id,
        cheat_type
    ) VALUES (
        in_event_time,
        in_fls_id,
        in_cheat_type
    );
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58470
f
log_event_solaris
in_function_oid oid, in_message dune.logmessagetype, in_controller_id bigint, in_solaris_balance bigint, in_solaris_delta bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.log_event_solaris(in_function_oid oid, in_message dune.logmessagetype, in_controller_id bigint, in_solaris_balance bigint, in_solaris_delta bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    partition_id BIGINT = 0;
	calling_function_name LogFunctionType;
	fls_id TEXT;
	fc_id BYTEA;
	char_name BYTEA;
BEGIN

    partition_id := coalesce(current_setting('dune.partition_id', true)::BIGINT, 0);

	-- map calling function name to LogFunctionType (each calling function must be added to LogFunctionType)
	SELECT proname::text::LogFunctionType
		INTO calling_function_name
		FROM pg_proc
		WHERE oid = in_function_oid;
	
	-- get the fls_id for the user performing the acction
	SELECT acc."user"
		INTO fls_id
		FROM accounts acc
		JOIN player_state ps on ps.account_id = acc.id
		WHERE ps.player_controller_id = in_controller_id
		LIMIT 1;

    INSERT INTO event_log (
        partition_id,
        category,
		function_name,
        message,
        event_time,
        meta
    ) VALUES (
        partition_id,
        'solaris',
		calling_function_name,
        in_message,
        now(),
        json_build_object('fls_id', fls_id, 'event', calling_function_name::text, 'solaris_balance', in_solaris_balance, 'solaris_delta', in_solaris_delta)
    );
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58471
f
login_account
in_user_id text, in_funcom_id text, in_platform_id text, in_platform_name text, in_minimum_returning_player_time_seconds integer, in_character_name text, in_return_dimension_index integer, in_home_dimension_index integer
SETOF dune.playerdescription

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.login_account(in_user_id text, in_funcom_id text, in_platform_id text, in_platform_name text, in_minimum_returning_player_time_seconds integer, in_character_name text, in_return_dimension_index integer, in_home_dimension_index integer)
 RETURNS SETOF dune.playerdescription
 LANGUAGE plpgsql
AS $function$
DECLARE
    user_account_id BigInt;
BEGIN
    PERFORM update_returning_player_status(in_user_id, in_minimum_returning_player_time_seconds);

    return query with
        acc as (
            INSERT INTO encrypted_accounts("id", "user", "platform_id", "platform_name", "encrypted_funcom_id")
                VALUES (default, in_user_id, in_platform_id, in_platform_name, encrypt_user_data(in_funcom_id))
                ON CONFLICT ("user") DO UPDATE SET
                    encrypted_funcom_id = excluded.encrypted_funcom_id,
                    platform_id = excluded.platform_id,
                    platform_name = excluded.platform_name
                RETURNING id, encrypted_accounts.user
        ),
        actor_ids as (
            -- TODO: unite this with accounts. One table to rule them all (until we want multiple chars per account)
            SELECT
                coalesce(player_controller_id, nextval('actors_id_seq')) as controller,
                coalesce(player_state_id, nextval('actors_id_seq')) as state,
                coalesce(player_pawn_id, nextval('actors_id_seq')) as pawn
            from acc left join player_state on player_state.account_id = acc.id
        ),
        actors_insert as (
            INSERT INTO actors("id", "owner_account_id")
            select unnest(array[controller, pawn, state]), acc.id from actor_ids, acc
            ON CONFLICT DO NOTHING
            returning id
        ),
        insert_actor_audit_log as (
            insert into actor_audit("id", "class")
                select
                    unnest(array[controller, pawn, state]) as id,
                    unnest(array['Controller', 'Pawn', 'State']) as clas
                from actor_ids
                on conflict do nothing
        ),
		demo as (
            UPDATE demo_users
                SET demo_state = CASE
                    WHEN demo_playtime_seconds IS NOT NULL THEN 'Demo'::DemoState
                    ELSE demo_state
                END
                WHERE fls_id = in_user_id
                RETURNING fls_id, demo_playtime_seconds, demo_state
		),
    	player_state_insert as (
            INSERT INTO encrypted_player_state(
            	"account_id", "encrypted_character_name", "online_status",
            	"player_controller_id", "player_pawn_id", "player_state_id",
				"return_dimension_index", "home_dimension_index", "last_login_time"
        	)
            select
                id, case
                    when in_character_name is not null then encrypt_user_data(in_character_name)
                    when encrypted_player_state.encrypted_character_name is null then encrypt_user_data('<TEMP>')
                    else encrypted_player_state.encrypted_character_name
                end,
                'Online', controller, pawn, state, in_return_dimension_index, in_home_dimension_index, now()
            from acc left join encrypted_player_state on acc.id = encrypted_player_state.account_id, actor_ids
            ON CONFLICT ("account_id")
            DO UPDATE SET
                online_status = 'Online',
                "return_dimension_index" = coalesce(in_return_dimension_index, encrypted_player_state.return_dimension_index),
                "home_dimension_index" = coalesce(in_home_dimension_index, encrypted_player_state.home_dimension_index),
                "last_login_time" = now()
            RETURNING
                account_id,
                "return_dimension_index",
                "home_dimension_index"
        ),
        inserted_count_dummy as (
            select count(*) from actors_insert
        ),
        player_actors as (
            select array_agg(full_actors.*) as actors
            from
                -- We need to refer 'returning' from inserts to ensure order of with statements
                inserted_count_dummy,
                actor_ids,
                load_full_actors(array[actor_ids.controller, actor_ids.state, actor_ids.pawn]) as full_actors
        ),
        pawn_info as (
            select id, (map, partition_id, dimension_index)::ServerInfo as server_info
            from actor_ids join actors on actors.id=actor_ids.pawn
        ),
        respawn_locations as (
            SELECT acc.id as account_id, get_respawn_locations(acc.id) as locations
            FROM acc
        ),
        this_player_tags as (
            select
                acc.id as account_id,
                array_agg(tag) as tags
            from acc join player_tags as tgs on tgs.account_id=acc.id
            group by acc.id
        ),
        keystones as (
            select player_id, array_agg(keystone_id) as purchased_keystones
            from purchased_specialization_keystones
            group by player_id
        ),
        tracks as (
            select player_id, array_agg(track_info) as progression_tracks
            from (
                select player_id, (track_type, xp_amount, level)::SpecializationTrackInfo as track_info
                from specialization_tracks
            )
            group by player_id
        ),
        journey_nodes as (
            SELECT acc.id as account_id, get_login_journey_nodes(acc.id) as journey_nodes_data
            FROM acc
        ),
        journey_nodes_cooldown as (
            SELECT acc.id as account_id, get_login_journey_nodes_cooldown(acc.id) as journey_nodes_cooldown_data
            FROM acc
        )
        select
            acc.id,
            player_actors.actors[1], player_actors.actors[2], player_actors.actors[3],
            coalesce(pawn_info.server_info, (null, null, null)::ServerInfo),
            (
                coalesce(respawn_locations.locations, array[]::RespawnLocation[]),
                player_state.pending_respawn_location_id
            )::RespawnInfo,
            coalesce(player_state.life_state, 'Alive'),
            coalesce(this_player_tags.tags, array[]::Text[]),
            player_state_insert.return_dimension_index,
            player_state.death_location,
            player_state_insert.home_dimension_index,
            demo.demo_state,
            demo.demo_playtime_seconds,
            (progression_tracks, purchased_keystones, refund_id)::SpecializationInfo,
            (
                coalesce(journey_nodes.journey_nodes_data, array[]::JourneyNodeInfo[]),
                coalesce(journey_nodes_cooldown.journey_nodes_cooldown_data, array[]::JourneyNodeCooldownInfo[]),
                coalesce(journey_tracked_cards.tracked_journey_card, ''),
                coalesce(journey_tracked_cards.tracked_landsraad_card, '')
            )::JourneyInfo,
            (player_state.last_returning_player_event_time AT TIME ZONE 'UTC')::TIMESTAMP,
            (player_state.last_returning_player_awarded_time AT TIME ZONE 'UTC')::TIMESTAMP
        from
            acc left join player_state on player_state.account_id = acc.id
                left join respawn_locations on respawn_locations.account_id = acc.id
                left join this_player_tags on this_player_tags.account_id = acc.id
                left join player_state_insert on player_state_insert.account_id = acc.id
                left join demo on demo.fls_id = acc.user
                left join journey_nodes on journey_nodes.account_id = acc.id
                left join journey_nodes_cooldown on journey_nodes_cooldown.account_id = acc.id
                left join journey_tracked_cards on journey_tracked_cards.player_id = player_state.player_controller_id
                left join keystones on keystones.player_id = player_state.player_controller_id
                left join tracks on tracks.player_id = player_state.player_controller_id
                left join specialization_refund_id on specialization_refund_id.player_id = player_state.player_controller_id,

            player_actors left join pawn_info on (player_actors.actors[3].id = pawn_info.id)
        limit 1;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58473
f
mark_server_dead
in_server_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.mark_server_dead(in_server_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	UPDATE farm_state SET alive = false WHERE server_id = in_server_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58474
f
merge_inventory_items
in_item_id bigint, in_dst_inventory_id bigint, in_dst_index bigint, in_count bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.merge_inventory_items(in_item_id bigint, in_dst_inventory_id bigint, in_dst_index bigint, in_count bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	remaining_stack_size BIGINT;
	item_data items%ROWTYPE;
	dst_item_data items%ROWTYPE;
BEGIN
	SELECT INTO STRICT item_data * FROM items WHERE id = in_item_id;

	SELECT INTO dst_item_data * FROM items WHERE inventory_id = in_dst_inventory_id AND position_index = in_dst_index;

	IF dst_item_data.id IS NULL THEN
		RETURN NULL;
	END IF;

	remaining_stack_size := item_data.stack_size - in_count;

	IF remaining_stack_size < 0 THEN
		RETURN NULL;
	END IF;


	IF item_data.template_id != dst_item_data.template_id THEN
		RETURN NULL;
	END IF;
    
    -- log item tracking
    PERFORM _add_item_trace_log('merge_inventory_items', in_item_id, in_dst_inventory_id, NULL, in_dst_index);
    
	IF remaining_stack_size > 0 THEN
		UPDATE items SET stack_size = remaining_stack_size WHERE id = in_item_id;
	ELSE
		PERFORM delete_item(in_item_id);
	END IF;
	UPDATE items SET stack_size = dst_item_data.stack_size + in_count WHERE id = dst_item_data.id;

	RETURN dst_item_data.id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58475
f
merge_or_move_inventory_item
in_item_id bigint, in_dst_inventory_id bigint, in_dst_index bigint, in_count bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.merge_or_move_inventory_item(in_item_id bigint, in_dst_inventory_id bigint, in_dst_index bigint, in_count bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	remaining_stack_size BIGINT;
	new_item_id BIGINT;
	item_data items%ROWTYPE;
	dst_item_data items%ROWTYPE;
BEGIN
	SELECT INTO new_item_id merge_inventory_items(in_item_id, in_dst_inventory_id, in_dst_index, in_count);

	IF new_item_id IS NULL THEN
		SELECT INTO new_item_id move_inventory_item(in_item_id, in_dst_inventory_id, in_dst_index, in_count);
	END IF;

	RETURN new_item_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58476
f
migrate_character
in_account_id bigint, home_dimension integer, max_solaris_allowed bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.migrate_character(in_account_id bigint, home_dimension integer, max_solaris_allowed bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    controller_id BIGINT;
    pawn_id BIGINT;
BEGIN
    SELECT player_controller_id, player_pawn_id into controller_id, pawn_id
    FROM player_state ps
    WHERE ps.account_id = in_account_id;

    UPDATE encrypted_player_state SET home_dimension_index = home_dimension WHERE account_id = in_account_id;

	UPDATE demo_users
	SET demo_state = 'DbMigratedToRetail'::DemoState, demo_playtime_seconds = NULL
    WHERE fls_id = (
		SELECT acc.user FROM accounts AS acc WHERE acc.id = in_account_id
		);

    PERFORM migrate_clamp_max_allow_solaris(pawn_id, max_solaris_allowed);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58477
f
migrate_clamp_max_allow_solaris
in_pawn_id bigint, max_solaris_allowed bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.migrate_clamp_max_allow_solaris(in_pawn_id bigint, max_solaris_allowed bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    with all_solaris_items as (
        SELECT row_number() over (), i.id, i.stack_size FROM  inventories inv
        JOIN items i on i.inventory_id = inv.id
        WHERE inv.actor_id = in_pawn_id AND inv.inventory_type = 0 AND i.template_id = 'SolarisCoin' -- inventory_type 0 is backpack
    ), total_solaris as (
        SELECT sum(all_solaris_items.stack_size) as total
        from all_solaris_items
    ), items_to_delete AS (
        SELECT array_agg(id) AS item_ids
        FROM all_solaris_items
        WHERE row_number > 1
    ), deleted_items AS (
        SELECT delete_items(item_ids)
        FROM items_to_delete
    )
    UPDATE items
    SET stack_size = LEAST(max_solaris_allowed, total_solaris.total)
    FROM all_solaris_items, total_solaris, deleted_items
    WHERE items.id = all_solaris_items.id AND all_solaris_items.row_number = 1;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58478
f
move_inventory_item
in_item_id bigint, in_dst_inventory_id bigint, in_dst_index bigint, in_count bigint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.move_inventory_item(in_item_id bigint, in_dst_inventory_id bigint, in_dst_index bigint, in_count bigint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	remaining_stack_size BIGINT;
	new_item_id BIGINT;
	item_data items%ROWTYPE;
BEGIN
	SELECT INTO STRICT item_data * FROM items WHERE id = in_item_id;

	remaining_stack_size := item_data.stack_size - in_count;

	IF remaining_stack_size < 0 THEN
		RETURN NULL;
	END IF;

    -- log item tracking
    PERFORM _add_item_trace_log('move_inventory_item', in_item_id, in_dst_inventory_id, NULL, in_dst_index);
        
	IF remaining_stack_size > 0 THEN
		item_data.stack_size := in_count;
		item_data.position_index := in_dst_index;
		item_data.inventory_id := in_dst_inventory_id;
		SELECT INTO item_data.id nextval('items_id_seq');
		INSERT INTO items VALUES(item_data.*) RETURNING id INTO new_item_id;
		UPDATE items SET stack_size = remaining_stack_size WHERE id = in_item_id;
	ELSE
		UPDATE items SET inventory_id = in_dst_inventory_id, position_index = in_dst_index WHERE id = in_item_id;
		new_item_id := in_item_id;
	END IF;

	RETURN new_item_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58479
f
overmap_delete_player_survival_data
in_player_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.overmap_delete_player_survival_data(in_player_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM overmap_players WHERE player_id = in_player_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58480
f
overmap_load_player_survival_data
in_player_id bigint
TABLE(out_vehicle_id bigint, out_has_polar_psu boolean, out_overmap_location dune.vector)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.overmap_load_player_survival_data(in_player_id bigint)
 RETURNS TABLE(out_vehicle_id bigint, out_has_polar_psu boolean, out_overmap_location dune.vector)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT vehicle_id, has_polar_psu, overmap_location FROM overmap_players WHERE player_id = in_player_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58481
f
overmap_save_player_survival_data
in_player_id bigint, in_vehicle_id bigint, in_has_polar_psu boolean, in_overmap_location dune.vector
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.overmap_save_player_survival_data(in_player_id bigint, in_vehicle_id bigint, in_has_polar_psu boolean, in_overmap_location dune.vector)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	real_vehicle_id BigInt;
BEGIN
	real_vehicle_id := (select id from actors where id=in_vehicle_id);
	INSERT INTO
		overmap_players(player_id, vehicle_id, has_polar_psu, overmap_location) VALUES(in_player_id, real_vehicle_id, in_has_polar_psu, in_overmap_location)
	ON CONFLICT(player_id)
		DO UPDATE SET
			vehicle_id = real_vehicle_id, has_polar_psu = in_has_polar_psu, overmap_location = in_overmap_location
		WHERE
			overmap_players.player_id = in_player_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58482
f
ownership_handle_actor_delete
in_player_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.ownership_handle_actor_delete(in_player_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	owned_totem_ids BIGINT[];
	actors_with_permission BIGINT[];
BEGIN

	-- Get owner entity ids (totems) where in_player_id is the owner
	SELECT ARRAY_AGG(owner_entity_id) into owned_totem_ids
	FROM permission_actor_rank
	JOIN permission_actor ON actor_id = permission_actor_id
	JOIN placeables on placeables.id = permission_actor_id
	WHERE player_id = in_player_id AND rank = 1::smallint; -- 1:owner

	-- Get actors where in_player_id is the owner
	SELECT ARRAY_AGG(permission_actor_id) into actors_with_permission
	FROM permission_actor_rank WHERE player_id = in_player_id AND rank = 1::smallint; -- 1:owner

	-- Remove all permissions for those actors
	IF cardinality(actors_with_permission) > 0 THEN
		DELETE FROM permission_actor_rank WHERE permission_actor_id = ANY(actors_with_permission);
		DELETE FROM MARKERS	WHERE marker_hash_id = ANY(actors_with_permission);

		PERFORM pg_notify('permission_notify_channel', format('owner_delete#{"PlayerId" : %s}', in_player_id));
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58483
f
parties_get_exclusive_operation_lock

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.parties_get_exclusive_operation_lock()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	PERFORM pg_advisory_xact_lock(9457135);  -- Parties in leet :/
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58484
f
perform_notify_on_character_delete
in_user_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.perform_notify_on_character_delete(in_user_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	PERFORM pg_notify('player_info_notify_channel', format('character_deleted#%s', in_user_id));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58485
f
permission_actor_create_or_update_base_marker
in_actor_id bigint, in_player_id bigint, in_rank smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.permission_actor_create_or_update_base_marker(in_actor_id bigint, in_player_id bigint, in_rank smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_marker_type TEXT := 'HomeBase';
	out_owner_id BIGINT;
	out_owner_name Text;
	out_dimension_index INTEGER;
	out_x REAL;
	out_y REAL;
	out_z REAL;
	out_totem_name Text;
	out_map_name Text;
	out_map_name_id SMALLINT;
	out_actor_type smallint;
BEGIN

	-- Get owner data
	SELECT player_id, character_name
	INTO out_owner_id, out_owner_name
	FROM permission_actor_rank
	JOIN player_state on player_controller_id = player_id
	WHERE rank = 1::smallint AND permission_actor_id = in_actor_id
	LIMIT 1;

	-- Get target and totem data
	SELECT dimension_index, (transform).location.x, (transform).location.y, (transform).location.z, actor_name, map, actor_type
	INTO out_dimension_index, out_x, out_y, out_z, out_totem_name, out_map_name, out_actor_type
	FROM permission_actor_rank
	JOIN permission_actor on actor_id = permission_actor_id
	JOIN player_state on player_controller_id = player_id
	JOIN actors ON permission_actor_id = actors.id
	WHERE permission_actor_id = in_actor_id	AND player_id = in_player_id
	LIMIT 1;

	SELECT map_name_id
	INTO out_map_name_id
	FROM map_names
	WHERE map_name = out_map_name;

	IF out_actor_type = 3 OR out_actor_type = 4 THEN -- Totem || TotemSmall
		INSERT INTO markers ("dimension_index", "marker_hash_id", "map_name_id", "marker", "area_id", "area_radius", "long_range", "payload")
		VALUES(out_dimension_index,
			in_actor_id,
			out_map_name_id,
			ROW(
				out_marker_type,
				out_x, out_y, out_z,
				'EMarkerPayloadType::Permissions'
			)::MARKER,
			0,
			0,
			FALSE,
			jsonb_build_object(
				'OwnerUID', out_owner_id,
				'OwnerName', out_owner_name,
				'TotemName', out_totem_name,
				'TotemId', in_actor_id
			)
		)
		ON CONFLICT (marker_hash_id, dimension_index, map_name_id)
		DO UPDATE SET
		marker = EXCLUDED.marker;

		INSERT INTO player_markers ("dimension_index", "player_id", "marker_hash_id", "map_name_id", "discovery_level", "discovery_method", "payload")
		VALUES(out_dimension_index,
			in_player_id,
			in_actor_id,
			out_map_name_id,
			3, -- EMarkerDiscoveryLevel::Discovered
			10, -- EMarkerDiscoveryMethod::Permissions
			'{}'::JSONB
		)
		ON CONFLICT (dimension_index, player_id, marker_hash_id, map_name_id)
		DO NOTHING;
	END IF;

END
$function$

@@@ENDRTN@@@
@@@RTN@@@58486
f
permission_actor_destroy
in_actor_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.permission_actor_destroy(in_actor_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM permission_actor_rank WHERE permission_actor_id = in_actor_id;
	DELETE FROM permission_actor WHERE actor_id = in_actor_id;
	-- Destroy map markers related with this actor
	DELETE FROM markers WHERE marker_hash_id = in_actor_id;
	DELETE FROM player_markers WHERE marker_hash_id = in_actor_id;

    PERFORM pg_notify('permission_notify_channel', format('destroy#{"ActorId" : %s}', in_actor_id));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58487
f
permission_actor_register
in_entry dune.actorpermissionentry, in_owner_rank dune.actorpermissionrankdata
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.permission_actor_register(in_entry dune.actorpermissionentry, in_owner_rank dune.actorpermissionrankdata)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO
		permission_actor("actor_id", "actor_name", "actor_type", "access_level", "is_child")
		VALUES(in_entry.actor_id, in_entry.actor_name, in_entry.actor_type, in_entry.access_level, in_entry.is_child);

	IF NOT in_entry.is_child THEN
		INSERT INTO permission_actor_rank("permission_actor_id", "player_id", "rank")
			VALUES(in_entry.actor_id, in_owner_rank.player_id, in_owner_rank.rank);
	END IF;

    -- there is no pg_notify here as the use cases where it may be needed are very low and we do not want to pay that cost. If we find any scenario where we need it, it can be added
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58488
f
permission_actor_takeover
in_entry dune.actorpermissionentry, in_owner_rank dune.actorpermissionrankdata
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.permission_actor_takeover(in_entry dune.actorpermissionentry, in_owner_rank dune.actorpermissionrankdata)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	found_actor_id BIGINT;
	found_guild_id BIGINT;
	found_previous_owner BIGINT;
BEGIN

	-- Check if the actor is already owned to avoid exploits
	SELECT player_id into found_previous_owner FROM permission_actor_rank WHERE permission_actor_id = in_entry.actor_id AND rank = 1::smallint;
	IF found_previous_owner IS NOT NULL THEN
		RAISE NOTICE 'Player % trying to claim ownership over actor % already owned by player %.',
			in_owner_rank.player_id, in_entry.actor_id, found_previous_owner;
		return;
	END IF;

	SELECT actor_id FROM permission_actor WHERE actor_id = in_entry.actor_id INTO found_actor_id;
	IF NOT FOUND THEN
		PERFORM permission_actor_register(in_entry, in_owner_rank);
		RETURN;
	END IF;

	SELECT guild_id FROM guild_members WHERE player_id = in_entry.actor_id INTO found_guild_id;
	IF NOT FOUND THEN
		found_guild_id := 0;
	END IF;

	DELETE FROM permission_actor_rank WHERE permission_actor_id = in_entry.actor_id;
	INSERT INTO permission_actor_rank("permission_actor_id", "player_id", "rank")
		VALUES(in_entry.actor_id, in_owner_rank.player_id, in_owner_rank.rank);

	PERFORM pg_notify('permission_notify_channel', format('takeover#{"ActorId" : %s , "PlayerId" : %s, "PlayerGuildId" : %s}', in_entry.actor_id, in_owner_rank.player_id, found_guild_id));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58489
f
permission_actor_update_marker_location
in_actor_id bigint, in_location_x real, in_location_y real, in_location_z real
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.permission_actor_update_marker_location(in_actor_id bigint, in_location_x real, in_location_y real, in_location_z real)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	UPDATE markers SET marker =
	(
		(marker).marker_type,
		in_location_x,
		in_location_y,
		in_location_z,
		(marker).payload_type
	)
	WHERE marker_hash_id = in_actor_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58490
f
permission_remove_player_rank
in_actor_id bigint, in_player_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.permission_remove_player_rank(in_actor_id bigint, in_player_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM permission_actor_rank WHERE permission_actor_id = in_actor_id AND player_id = in_player_id;

	-- Remove from player_markers using player id and, actor id as hash id
	DELETE FROM player_markers WHERE player_id = in_player_id AND marker_hash_id = in_actor_id;

	PERFORM pg_notify('permission_notify_channel', format('remove_rank#{"ActorId" : %s , "PlayerId" : %s}', in_actor_id, in_player_id));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58491
f
permission_set_access_level
in_actor_id bigint, in_access_level smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.permission_set_access_level(in_actor_id bigint, in_access_level smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	UPDATE permission_actor SET access_level = in_access_level WHERE permission_actor.actor_id = in_actor_id;

     PERFORM pg_notify('permission_notify_channel', format('set_access_level#{"ActorId" : %s , "AccessLevel" : %s}', in_actor_id, in_access_level));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58492
f
permission_set_name
in_actor_id bigint, in_name text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.permission_set_name(in_actor_id bigint, in_name text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	UPDATE permission_actor SET actor_name = in_name WHERE permission_actor.actor_id = in_actor_id;

	UPDATE markers SET marker =
	(
		(marker).marker_type,
		(marker).x,
		(marker).y,
		(marker).z,
		(marker).payload_type
	),
	payload = jsonb_set(payload, '{TotemName}', to_jsonb(in_name) , false)
	WHERE marker_hash_id = in_actor_id;

    PERFORM pg_notify('permission_notify_channel', format('set_name#{"ActorId" : %s , "Name" : "%s"}', in_actor_id, in_name));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58493
f
permission_set_player_rank
in_actor_id bigint, in_player_id bigint, in_rank smallint, in_map_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.permission_set_player_rank(in_actor_id bigint, in_player_id bigint, in_rank smallint, in_map_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	found_actor_id BIGINT;
	found_guild_id BIGINT;
BEGIN
	SELECT permission_actor_id FROM permission_actor_rank WHERE permission_actor_id = in_actor_id AND player_id = in_player_id INTO found_actor_id;
	IF NOT FOUND THEN
    	INSERT INTO permission_actor_rank("permission_actor_id", "player_id", "rank") VALUES(in_actor_id, in_player_id, in_rank);
    ELSE
	    UPDATE permission_actor_rank SET rank = in_rank WHERE permission_actor_rank.permission_actor_id = in_actor_id AND player_id = in_player_id;
	END IF;

	SELECT guild_id FROM guild_members WHERE player_id = in_actor_id INTO found_guild_id;
	IF NOT FOUND THEN
		found_guild_id := 0;
	END IF;

	PERFORM permission_actor_create_or_update_base_marker(in_actor_id, in_player_id, in_rank);

    PERFORM pg_notify('permission_notify_channel', format('set_rank#{"ActorId" : %s , "PlayerId" : %s, "PlayerGuildId" : %s, "Rank" : %s, "Map" : %s}', in_actor_id, in_player_id, found_guild_id, in_rank, in_map_id));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58494
f
player_purchased_item_from_vendor
in_vendor_id text, in_player_id bigint, in_template_id text, in_amount_bought integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.player_purchased_item_from_vendor(in_vendor_id text, in_player_id bigint, in_template_id text, in_amount_bought integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	-- Add/update stock
	IF NOT EXISTS
		( SELECT * FROM vendor_stock_state
		WHERE vendor_id = in_vendor_id and player_id = in_player_id AND template_id = in_template_id)
	THEN
		INSERT INTO vendor_stock_state(vendor_id, player_id, template_id, amount_bought) VALUES(in_vendor_id, in_player_id, in_template_id, in_amount_bought);
	ELSE
		UPDATE vendor_stock_state SET amount_bought = amount_bought + in_amount_bought
		WHERE vendor_id = in_vendor_id and player_id = in_player_id AND template_id = in_template_id;
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58495
f
player_state_update
in_data dune.playerstateupdatedata[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.player_state_update(in_data dune.playerstateupdatedata[])
 RETURNS void
 LANGUAGE sql
AS $function$
    -- online -> offline
    with
        update_data as (select * from unnest(in_data))
        update encrypted_player_state as ps
            set online_status = update_data.online_status
            from update_data
            where ps.player_controller_id = update_data.player_controller_id
                and ps.server_id = update_data.current_server_id -- make sure we don't update if the player is already online somewhere else
                and update_data.online_status != 'Online'
                and ps.online_status != update_data.online_status; -- avoid unnecessary data changes

    -- offline -> online
	with
		update_data as (select * from unnest(in_data))
        update encrypted_player_state as ps
            set online_status = update_data.online_status,
                server_id = update_data.current_server_id
            from update_data
            where ps.player_controller_id = update_data.player_controller_id
              and update_data.online_status = 'Online'
              and (ps.server_id is null or ps.server_id != update_data.current_server_id or ps.online_status != update_data.online_status); -- avoid unnecessary data changes

	with
		update_data as (select * from unnest(in_data))
        update encrypted_player_state as ps
            set reconnect_grace_period_end = update_data.reconnect_grace_period_end
            from update_data
            where ps.player_controller_id = update_data.player_controller_id
                and not update_data.reconnect_grace_period_end is null;

	with
		update_data as (select * from unnest(in_data))
        update encrypted_player_state as ps
            set
                last_avatar_activity = (update_data.on_disconnect).last_online_time AT TIME ZONE 'UTC',
                previous_server_partition_id = (update_data.on_disconnect).previous_server_partition_id
            from update_data
            where ps.player_controller_id = update_data.player_controller_id
                and not update_data.on_disconnect is null;
$function$

@@@ENDRTN@@@
@@@RTN@@@58496
f
pledge_guild_allegiance
in_guild_id bigint, in_guild_leader_player_id bigint, in_neutral_faction_id smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.pledge_guild_allegiance(in_guild_id bigint, in_guild_leader_player_id bigint, in_neutral_faction_id smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	guilds_changed SMALLINT := 0;
	guild_data_record record;
	guild_leader_record record;
	guild_leader_faction_id SMALLINT;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	SELECT * INTO guild_leader_record FROM guild_members
	LEFT JOIN player_faction ON actor_id = in_guild_leader_player_id
	WHERE player_id = in_guild_leader_player_id AND is_player_guild_admin(in_guild_leader_player_id, in_guild_id);

	IF guild_leader_record IS NULL THEN
		RAISE EXCEPTION 'Trying to change a faction for a player: % without a guild', in_guild_leader_player_id;
	END IF;

	SELECT * INTO guild_data_record FROM guilds WHERE guild_id = in_guild_id;
	IF guild_data_record IS NULL THEN
		RAISE EXCEPTION 'Trying to change a faction in non existing guild: %', in_guild_id;
	END IF;

	IF guild_leader_record.faction_id IS NULL THEN
		guild_leader_faction_id := in_neutral_faction_id;
    ELSE
		guild_leader_faction_id := guild_leader_record.faction_id;
	END IF;

	if guild_leader_faction_id = in_neutral_faction_id THEN
		RAISE EXCEPTION 'Guild leader has neutral faction, cannot change faction to neutral';
	ELSEIF guild_data_record.guild_faction = guild_leader_faction_id THEN
		RAISE EXCEPTION 'Guild already has the same allegiance: % as the guild leader %', in_guild_id, guild_data_record.guild_faction;
	END IF;

	UPDATE guilds SET guild_faction = guild_leader_faction_id WHERE guilds.guild_id = in_guild_id;

	PERFORM pg_notify('guild_notify_channel', format('pledge_guild_allegiance#{"GuildId" : %s , "OldGuildFactionDbId" : %s, "NewGuildFactionDbId" : %s}', in_guild_id, guild_data_record.guild_faction, guild_leader_faction_id));
	PERFORM remove_guild_members(ARRAY(
		SELECT player_id FROM guild_members
		JOIN player_faction ON guild_members.player_id = player_faction.actor_id
		WHERE guild_leader_faction_id != player_faction.faction_id AND player_faction.faction_id != in_neutral_faction_id AND guild_members.guild_id = in_guild_id),
		in_guild_id,
		2::smallint
	);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58497
f
produce_spicefield_manifest
in_map_name text, in_dimension_index integer
TABLE(server text, type_id integer, inactive_fields integer, requested_fields integer)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.produce_spicefield_manifest(in_map_name text, in_dimension_index integer)
 RETURNS TABLE(server text, type_id integer, inactive_fields integer, requested_fields integer)
 LANGUAGE plpgsql
AS $function$
begin
	return query
	select sa.server_id, sa.spicefield_type_id, sa.inactive_fields_of_type, sa.requested_spawned_of_type
	from spicefield_server_availability sa join spicefield_types st
	on sa.spicefield_type_id = st.spicefield_type_id
	where st.map_name = in_map_name and st.dimension_index = in_dimension_index
	order by server_id;
end $function$

@@@ENDRTN@@@
@@@RTN@@@58498
f
promote_guild_member
in_guild_id bigint, in_player_id bigint, in_new_role smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.promote_guild_member(in_guild_id bigint, in_player_id bigint, in_new_role smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	-- check if new admin is actualy in guild
	IF NOT EXISTS(SELECT FROM guild_members WHERE player_id = in_player_id AND guild_id = in_guild_id) THEN
    	RAISE EXCEPTION 'Trying to promte player not in guild %.', in_player_id;
	END IF;

	if in_new_role = 100 THEN
		-- set admin to member
		UPDATE guild_members SET role_id = 50 WHERE guild_id = in_guild_id AND role_id = 100;
	END IF;

	-- set new player to new role
	UPDATE guild_members SET role_id = in_new_role WHERE player_id = in_player_id AND guild_id = in_guild_id;

	PERFORM pg_notify('guild_notify_channel', format('promote_player#{"PlayerId" : %s , "GuildId" : %s, "NewRole" : %s}', in_player_id, in_guild_id, in_new_role));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58499
f
promote_new_party_leader
in_party_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.promote_new_party_leader(in_party_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_current_leader BIGINT;
	out_new_leader BIGINT;
BEGIN
	PERFORM parties_get_exclusive_operation_lock();

	-- get current leader
	SELECT party_leader_id FROM parties where party_id = in_party_id into out_current_leader;
	IF out_current_leader IS NULL THEN
		RAISE EXCEPTION 'Promoting a player to a non existing party %.', in_party_id;
	END IF;

	-- get first member in the party that is online and is not the party leader
	SELECT party_members.player_id INTO out_new_leader FROM party_members
	JOIN player_state ON player_state.player_controller_id = party_members.player_id
	WHERE party_members.party_id = in_party_id
	AND party_members.player_id <> out_current_leader
	AND player_state.online_status = 'Online';

	IF out_new_leader IS NOT NULL THEN
		-- promote
		UPDATE parties SET party_leader_id = out_new_leader WHERE party_id = in_party_id;
		PERFORM pg_notify('party_notify_channel', format('promote_party_leader#{"PartyId" : %s, "PlayerId" : %s}', in_party_id, out_new_leader));
	END IF;

END
$function$

@@@ENDRTN@@@
@@@RTN@@@58500
f
promote_party_leader_to
in_party_id bigint, in_player_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.promote_party_leader_to(in_party_id bigint, in_player_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_current_leader BIGINT;
	out_new_leader BIGINT;
BEGIN
	PERFORM parties_get_exclusive_operation_lock();

	-- get current leader
	SELECT party_leader_id FROM parties where party_id = in_party_id into out_current_leader;
	IF out_current_leader IS NULL THEN
		RAISE EXCEPTION 'Promoting a player to a non existing party %.', in_party_id;
	END IF;

	-- get new member if it is online
	SELECT party_members.player_id INTO out_new_leader FROM party_members
	JOIN player_state ON player_state.player_controller_id = party_members.player_id
	WHERE party_members.party_id = in_party_id
	AND party_members.player_id = in_player_id
	AND player_state.online_status = 'Online';

	IF out_new_leader IS NOT NULL THEN
		-- promote
		UPDATE parties SET party_leader_id = out_new_leader WHERE party_id = in_party_id;
		PERFORM pg_notify('party_notify_channel', format('promote_party_leader#{"PartyId" : %s, "PlayerId" : %s}', in_party_id, out_new_leader));
	ELSE
		RAISE EXCEPTION 'Promopromoting player %, which is offline or does not belong to the party.', in_party_id;
		-- pg_notify for feedback?
	END IF;

END
$function$

@@@ENDRTN@@@
@@@RTN@@@58501
f
purchase_specialization_keystone
in_player_id bigint, in_keystone text
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.purchase_specialization_keystone(in_player_id bigint, in_keystone text)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
DECLARE
	found_id SMALLINT;
	inserted_id SMALLINT;
BEGIN
	SELECT id FROM specialization_keystones_map INTO found_id WHERE name = in_keystone;
	IF found_id IS NULL THEN
		RETURN FALSE;
	END IF;
	
	INSERT INTO purchased_specialization_keystones (player_id, keystone_id) VALUES (in_player_id, found_id)
	ON CONFLICT DO NOTHING
	RETURNING keystone_id INTO inserted_id;
	
	IF inserted_id IS NULL THEN
		RETURN FALSE;
	END IF;
	
	RETURN TRUE;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58502
f
record_deactivated_spice_field
in_server_id text, in_spicefield_type_id integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.record_deactivated_spice_field(in_server_id text, in_spicefield_type_id integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE spicefield_server_availability
	SET inactive_fields_of_type = inactive_fields_of_type + 1
	WHERE server_id = in_server_id AND spicefield_type_id = in_spicefield_type_id;

	UPDATE spicefield_types
	SET current_globally_active = current_globally_active - 1
	WHERE spicefield_type_id = in_spicefield_type_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58503
f
record_dungeon_completion
in_dungeon_id text, in_difficulty integer, in_duration_ms integer, players_ids bigint[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.record_dungeon_completion(in_dungeon_id text, in_difficulty integer, in_duration_ms integer, players_ids bigint[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	new_completion_id BIGINT;
begin
	INSERT INTO dungeon_completion VALUES (DEFAULT, in_dungeon_id, in_difficulty, in_duration_ms, array_length(players_ids, 1)) 
		RETURNING completion_id INTO new_completion_id; 
    INSERT INTO dungeon_completion_players SELECT t.player_id, new_completion_id FROM UNNEST(players_ids) AS t(player_id);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58504
f
record_logoff_persistence_end_time
in_player_pawn_id bigint, in_logoff_persistence_end_time timestamp without time zone
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.record_logoff_persistence_end_time(in_player_pawn_id bigint, in_logoff_persistence_end_time timestamp without time zone)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE encrypted_player_state
    SET logoff_persistence_end_time = in_logoff_persistence_end_time
    WHERE player_pawn_id = in_player_pawn_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58505
f
record_static_shifting_sand
in_id text, in_alpha double precision, in_x double precision, in_y double precision, in_last_modified_time bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.record_static_shifting_sand(in_id text, in_alpha double precision, in_x double precision, in_y double precision, in_last_modified_time bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO shiftingsands_data(id, alpha, x, y, last_modified_time) Values(in_id, in_alpha, in_x, in_y, to_timestamp(in_last_modified_time))
	ON CONFLICT(id) DO UPDATE SET alpha = $2, last_modified_time = to_timestamp(in_last_modified_time);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58506
f
record_unreadied_spice_fields
in_server_id text, in_spicefield_type_id integer, in_num_unreadied integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.record_unreadied_spice_fields(in_server_id text, in_spicefield_type_id integer, in_num_unreadied integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE spicefield_types
	SET current_globally_primed = current_globally_primed - in_num_unreadied
	WHERE spicefield_type_id = in_spicefield_type_id;

	UPDATE spicefield_server_availability
	SET inactive_fields_of_type = inactive_fields_of_type + in_num_unreadied
	WHERE server_id = in_server_id AND spicefield_type_id = in_spicefield_type_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58507
f
register_lore_pickup
in_lore_pickup_ids text[]
SETOF smallint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.register_lore_pickup(in_lore_pickup_ids text[])
 RETURNS SETOF smallint
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN query WITH
		input AS (
			SELECT UNNEST (in_lore_pickup_ids) as lore_pickup_id
		),
		existing AS (
			SELECT incremental_id, lore_pickup_id FROM lore_pickups WHERE lore_pickup_id = ANY(SELECT lore_pickup_id FROM input)
		),
		inserted AS (
			INSERT INTO lore_pickups("lore_pickup_id") SELECT lore_pickup_id FROM input WHERE NOT lore_pickup_id = ANY(SELECT lore_pickup_id FROM existing) returning incremental_id, lore_pickup_id
		),
		combined AS (
            SELECT incremental_id, lore_pickup_id FROM existing UNION ALL SELECT incremental_id, lore_pickup_id FROM inserted
        )
    SELECT incremental_id FROM combined ORDER BY lore_pickup_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58508
f
register_new_factions
factions text[]
TABLE(faction_id smallint, faction_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.register_new_factions(factions text[])
 RETURNS TABLE(faction_id smallint, faction_name text)
 LANGUAGE plpgsql
AS $function$
DECLARE
	found_role_id SMALLINT;
BEGIN
     -- Lock the factions table to prevent concurrent modifications. This is only done once on server start up.
    LOCK TABLE factions IN SHARE ROW EXCLUSIVE MODE;
    WITH new_factions AS (
        SELECT f FROM UNNEST(factions) f LEFT JOIN factions ON f = factions.name WHERE id IS NULL
    )
	INSERT INTO factions (name) SELECT * FROM new_factions ON CONFLICT DO NOTHING;
	RETURN QUERY SELECT * from factions;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58509
f
register_new_tutorials
tutorials text[]
TABLE(tutorial_id smallint, tutorial_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.register_new_tutorials(tutorials text[])
 RETURNS TABLE(tutorial_id smallint, tutorial_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
    -- Lock the tutorials table to prevent concurrent modifications. This is only done once on server start up.
    LOCK TABLE tutorials IN SHARE ROW EXCLUSIVE MODE;

    WITH new_tutorials AS (
        SELECT q FROM unnest(tutorials) q LEFT JOIN tutorials ON q = tutorials.name WHERE id is NULL
    )
    INSERT INTO tutorials (name) SELECT * FROM new_tutorials;

    RETURN QUERY SELECT * from tutorials;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58510
f
register_per_player_lore_pickup
in_lore_pickup_ids text[], in_use_temporary boolean
SETOF smallint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.register_per_player_lore_pickup(in_lore_pickup_ids text[], in_use_temporary boolean)
 RETURNS SETOF smallint
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF in_use_temporary THEN
		RETURN query select * from register_temporary_lore_pickup(in_lore_pickup_ids);
	ELSE
		RETURN query select * from register_lore_pickup(in_lore_pickup_ids);
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58511
f
register_spawned_actor
in_spawner_id bigint, in_actor_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.register_spawned_actor(in_spawner_id bigint, in_actor_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO actor_spawner_actors(spawner_id, actor_id) VALUES(in_spawner_id, in_actor_id);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58512
f
register_spice_field_server_resources
in_server_id text, in_spicefield_type_ids integer[], in_inactive_fields_of_types integer[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.register_spice_field_server_resources(in_server_id text, in_spicefield_type_ids integer[], in_inactive_fields_of_types integer[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO spicefield_server_availability(server_id, spicefield_type_id, inactive_fields_of_type)
	SELECT in_server_id, unnest(in_spicefield_type_ids), unnest(in_inactive_fields_of_types)
	ON CONFLICT(server_id, spicefield_type_id)
	DO UPDATE SET server_id = excluded.server_id, spicefield_type_id = excluded.spicefield_type_id, inactive_fields_of_type = excluded.inactive_fields_of_type;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58513
f
register_temporary_lore_pickup
in_lore_pickup_ids text[]
SETOF smallint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.register_temporary_lore_pickup(in_lore_pickup_ids text[])
 RETURNS SETOF smallint
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN query WITH
		input AS (
			SELECT UNNEST (in_lore_pickup_ids) as lore_pickup_id
		),
		existing AS (
			SELECT incremental_id, lore_pickup_id FROM lore_pickups_temporary WHERE lore_pickup_id = ANY(SELECT lore_pickup_id FROM input)
		),
		inserted AS (
			INSERT INTO lore_pickups_temporary("lore_pickup_id") SELECT lore_pickup_id FROM input WHERE NOT lore_pickup_id = ANY(SELECT lore_pickup_id FROM existing) returning incremental_id, lore_pickup_id
		),
		combined AS (
            SELECT incremental_id, lore_pickup_id FROM existing UNION ALL SELECT incremental_id, lore_pickup_id FROM inserted
        )
    SELECT incremental_id FROM combined ORDER BY lore_pickup_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58514
f
reject_guild_invite
in_invite_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.reject_guild_invite(in_invite_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	player_id BIGINT := 0;
	guild_id BIGINT := 0;
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	-- check if invite exists
	SELECT guild_invites.player_id, guild_invites.guild_id FROM guild_invites WHERE invite_id = in_invite_id INTO player_id, guild_id;
	IF NOT FOUND THEN
    	RAISE EXCEPTION 'Trying to remove non exiting invite %.', in_invite_id;
	END IF;

	DELETE FROM guild_invites WHERE invite_id = in_invite_id;

	PERFORM pg_notify('guild_notify_channel', format('reject_invite#{"PlayerId" : %s , "GuildId" : %s}', player_id, guild_id));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58515
f
remove_aborted_authority_transfer_actors
in_partition_id bigint
SETOF dune.actorspawninfo

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_aborted_authority_transfer_actors(in_partition_id bigint)
 RETURNS SETOF dune.actorspawninfo
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
        WITH removed_actors AS (
            DELETE FROM actor_state WHERE actor_state.state = 'AbortedAuthorityTransfer'
            RETURNING actor_state.actor_id
        )
        SELECT a.id, a.class AS class_name, a.transform, a.partition_id, a.dimension_index
        FROM actors AS a
        INNER JOIN removed_actors ON a.id = removed_actors.actor_id
        WHERE a.partition_id = in_partition_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58516
f
remove_character_transfer_state
in_fls_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_character_transfer_state(in_fls_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM character_transfer_imports WHERE fls_id = in_fls_id;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58517
f
remove_communinet_player_channel
in_account_id bigint, in_channel_name text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_communinet_player_channel(in_account_id bigint, in_channel_name text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM communinet_player_channels WHERE account_id = in_account_id AND channel_name = in_channel_name;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58518
f
remove_guild_members
in_player_ids bigint[], in_guild_id bigint, in_remove_reason smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_guild_members(in_player_ids bigint[], in_guild_id bigint, in_remove_reason smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_guild_name TEXT;
	players_removed BIGINT[];
BEGIN
	PERFORM guilds_get_exclusive_operation_lock();

	-- check if guild exists
	SELECT guild_name INTO out_guild_name FROM guilds WHERE guild_id = in_guild_id;
	IF NOT FOUND THEN
    	RAISE EXCEPTION 'Trying to disband non existing guild %.', in_guild_id;
	END IF;

	WITH removed_members AS (
		DELETE FROM guild_members
		WHERE player_id = ANY(in_player_ids) AND NOT is_player_guild_admin(player_id, in_guild_id)
		RETURNING *
	) SELECT array_agg(player_id) from removed_members INTO players_removed;

	PERFORM pg_notify('guild_notify_channel', format('remove_players#{"PlayerIds" : [%s] , "GuildId" : %s, "GuildName" : "%s", "GuildRemoveReason" : %s}', ARRAY_TO_STRING(players_removed, ','), in_guild_id, out_guild_name, in_remove_reason));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58519
f
remove_items
items_to_remove text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_items(items_to_remove text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
    PERFORM delete_items(
        (
            SELECT array_agg(id)
            FROM items
            WHERE template_id = ANY (items_to_remove)
        )
    );
end;
$function$

@@@ENDRTN@@@
@@@RTN@@@58520
f
remove_items_and_recipes
items_to_remove text[], recipes_to_remove text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_items_and_recipes(items_to_remove text[], recipes_to_remove text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
declare
    items_to_remove_filtered text[];
    recipes_to_remove_filtered text[];
begin
    lock table removed_items, removed_recipes, actors, fgl_entities, items in exclusive mode;
    
    -- get the items in items_to_remove that are not in removed_items
    items_to_remove_filtered := get_items_to_remove(items_to_remove);
    
    -- remove items from fgl entities
    perform remove_items_or_recipes_from_fgl_entities(items_to_remove_filtered);
    
    -- get the recipes in recipes_to_remove that are not in removed_recipes
    recipes_to_remove_filtered := get_recipes_to_remove(recipes_to_remove);
    
    -- removes the requests that contain items or recipes from fgl_entities.
    perform remove_items_or_recipes_from_fgl_entities(recipes_to_remove_filtered);
    
    -- delete the items
    perform remove_items(items_to_remove_filtered);

    -- remove the recipes from actors known item recipes
    perform remove_recipes_from_actor_properties(recipes_to_remove_filtered);
    
    -- insert removed items and recipes in their respective tables
    perform update_removed_items_and_recipes(items_to_remove_filtered, recipes_to_remove_filtered);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58521
f
remove_items_or_recipes_from_fgl_entities
item_or_recipes text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_items_or_recipes_from_fgl_entities(item_or_recipes text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$ 
declare
   item_or_recipe text;
   ent_id bigint;
   components_data jsonb;
   components_data_updated jsonb;
   request jsonb;
   item_or_recipe_found boolean;
   updated_requests jsonb;
   ingredient_allocation jsonb;
   item_alloc_node jsonb;
   allocated_item jsonb;
   allocated_ingredient_info record;
begin
   drop table if exists allocated_ingredients_info_temp;
   create temp table allocated_ingredients_info_temp (item_unique_id bigint, amount bigint);
   if array_length(item_or_recipes, 1) is null then
      return;
   end if;
   foreach item_or_recipe in array(item_or_recipes)
   loop
      for ent_id, components_data in select entity_id, components
                                    from fgl_entities
                                    where components->'FItemCraftingComponent' is not null
      loop
         components_data_updated := components_data;
         if jsonb_array_length(components_data_updated->'FItemCraftingComponent'->1->'RequestsQueue') < 1 then
            return;
         end if;
         loop
            item_or_recipe_found := false;
            for request in select value::jsonb
                            from jsonb_array_elements(components_data_updated->'FItemCraftingComponent'->1->'RequestsQueue')
            loop
               if (request->'RecipeId'->>'Name' = item_or_recipe or
                  exists (select 1 from jsonb_array_elements(request->'ResultItems') where value->'ItemTemplateId'->>'Name' = item_or_recipe) or
                  exists (select 1 from jsonb_array_elements(request->'IngredientAllocations') where value->'ItemAllocNodes'->0->'ItemTemplateId'->>'Name' = item_or_recipe)) then
                  insert into allocated_ingredients_info_temp (item_unique_id, amount)
                    select (item->>'ItemUniqueId')::BigInt, (item->>'ItemAmount')::BigInt from (
                        select jsonb_array_elements(node->'AllocatedItems') as item, node as outer_node from (
                            select jsonb_array_elements(allocation->'ItemAllocNodes') as node from (
                                select jsonb_array_elements(request->'IngredientAllocations') as allocation
                            ) allocations
                        ) nodes
                    ) items;
                  updated_requests := (select jsonb_agg(value) from jsonb_array_elements(components_data_updated->'FItemCraftingComponent'->1->'RequestsQueue') where value::jsonb != request);
                  if updated_requests is null then
                     components_data_updated := jsonb_set(components_data_updated, '{FItemCraftingComponent,1,RequestsQueue}', '[]'::jsonb);
                     components_data_updated := jsonb_set(components_data_updated, '{FItemCraftingComponent,1,State}', '"Idle"'::jsonb);
                     components_data_updated := jsonb_set(components_data_updated, '{FItemCraftingComponent,1,TotalTimeToCraftInSec}', '0'::jsonb);
                     components_data_updated := jsonb_set(components_data_updated, '{FItemCraftingComponent,1,PreviouslyCompletedTimeToCraftInSec}', '0'::jsonb);
                  else
                     components_data_updated := jsonb_set(components_data_updated, '{FItemCraftingComponent,1,RequestsQueue}', updated_requests);
                  end if;
                  item_or_recipe_found := true;
                  exit;
               end if;
            end loop;
            exit when not item_or_recipe_found;
         end loop;
         update fgl_entities
         set components = components_data_updated
         where entity_id = ent_id;
      end loop;
   end loop;
   
   for allocated_ingredient_info in select * from allocated_ingredients_info_temp
   loop
      perform delete_inventory_item(allocated_ingredient_info.item_unique_id, allocated_ingredient_info.amount);
   end loop;
   
   drop table allocated_ingredients_info_temp;
end;
$function$

@@@ENDRTN@@@
@@@RTN@@@58522
f
remove_members_offline_for
in_interval_seconds integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_members_offline_for(in_interval_seconds integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	PERFORM parties_get_exclusive_operation_lock();

	PERFORM remove_party_member(party_members.player_id, 0::SMALLINT) FROM party_members
	JOIN player_state ON player_state.player_controller_id = party_members.player_id
	WHERE player_state.online_status = 'Offline'
	AND CURRENT_TIMESTAMP > player_state.last_avatar_activity + INTERVAL '1 second' * in_interval_seconds;

END
$function$

@@@ENDRTN@@@
@@@RTN@@@58523
f
remove_party_invite
in_invite_id bigint, in_remove_reason smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_party_invite(in_invite_id bigint, in_remove_reason smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_sender_player_id BIGINT;
	out_player_id BIGINT;
BEGIN
	-- delete invite
	IF in_invite_id IS NOT NULL THEN
		DELETE FROM party_invites WHERE invite_id = in_invite_id RETURNING player_id, sender_player_id INTO out_player_id, out_sender_player_id;
		PERFORM pg_notify('party_notify_channel', format('remove_invite#{"InviteId" : %s, "Reason" : %s, "SenderId" : %s, "PlayerId" : %s}', in_invite_id, in_remove_reason, out_sender_player_id, out_player_id));
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58524
f
remove_party_member
in_player_id bigint, in_remove_reason smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_party_member(in_player_id bigint, in_remove_reason smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	out_party_id BIGINT;
	out_player_platform_name TEXT;
	member_count BIGINT;
	out_platform_members_count BIGINT;
	removed_player_id BIGINT;
	removed_is_leader BOOLEAN;
BEGIN
	PERFORM parties_get_exclusive_operation_lock();

	DELETE FROM party_members WHERE player_id = in_player_id RETURNING player_id, party_id INTO removed_player_id, out_party_id;

	IF removed_player_id IS NOT NULL THEN
		-- check if there are more than 1 player in the party
		SELECT INTO member_count COUNT(*) FROM party_members WHERE party_id = out_party_id;
		IF member_count > 1 THEN
			-- check if removed player is leader
			SELECT INTO removed_is_leader EXISTS (SELECT 1 FROM parties WHERE party_leader_id = removed_player_id);
			IF removed_is_leader THEN
				-- TODO promote player other player
				PERFORM promote_new_party_leader(out_party_id);
			END IF;
		END IF;

		SELECT accounts.platform_name INTO out_player_platform_name FROM accounts 
		JOIN actors ON actors.id = in_player_id
		WHERE accounts.id = actors.owner_account_id;

		SELECT num_of_players INTO out_platform_members_count FROM platform_parties_mapping WHERE platform_name = out_player_platform_name AND dune_party_id = out_party_id;
		IF out_platform_members_count IS NOT NULL THEN
			-- there was a platform session for the player's party
			IF out_platform_members_count <= 1 THEN
				-- if player leaving causes no players to be in that platform session anymore, remove entry
				DELETE FROM platform_parties_mapping WHERE platform_name = out_player_platform_name AND dune_party_id = out_party_id;
			ELSE
				-- still players, decrease platform player count
				UPDATE platform_parties_mapping SET num_of_players = out_platform_members_count-1 WHERE platform_name = out_player_platform_name AND dune_party_id = out_party_id;
			END IF;
		END IF;

		PERFORM pg_notify('party_notify_channel', format(
			'remove_party_member#{"PlayerId" : %s, "PartyId" : %s, "PlayerPlatformName" : "%s", "PartyRemoveReason" : %s}', 
			removed_player_id, out_party_id, out_player_platform_name, in_remove_reason));

		If member_count <= 1 THEN
			PERFORM disband_party(out_party_id);
		END IF;

	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58525
f
remove_recipes_from_actor_properties
recipes_to_remove text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_recipes_from_actor_properties(recipes_to_remove text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
    with actors_properties as (
        select id, properties->'CraftingRecipesLibraryActorComponent'->'m_KnownItemRecipes' as recipes
        from actors
    ),
    modified_actors_properties as (
        select id, (
            select jsonb_agg(recipe)
            from jsonb_array_elements(actors_properties.recipes) as recipe
            where not (recipe->'BaseRecipeId'->>'Name' = any(recipes_to_remove))
        ) as filtered_recipes
        from actors_properties
    )
    update actors
    set properties = jsonb_set(
        actors.properties,
        '{CraftingRecipesLibraryActorComponent,m_KnownItemRecipes}',
        coalesce(modified_actors_properties.filtered_recipes, '[]'::jsonb)
    )
    from modified_actors_properties
    where actors.id = modified_actors_properties.id;
end;
$function$

@@@ENDRTN@@@
@@@RTN@@@58526
f
remove_resourcefield_states
in_map text, in_dimension_index integer, in_field_ids bigint[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.remove_resourcefield_states(in_map text, in_dimension_index integer, in_field_ids bigint[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE 
	FROM resourcefield_state 
	WHERE field_id = ANY(in_field_ids) AND map = in_map AND dimension_index = in_dimension_index;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58527
f
request_spawn_spice_field
in_server_id text, in_spicefield_type_id integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.request_spawn_spice_field(in_server_id text, in_spicefield_type_id integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM spicefield_type_id FROM spicefield_types AS t
		WHERE t.spicefield_type_id = in_spicefield_type_id AND t.is_spawning_active IS TRUE AND t.current_globally_primed < t.max_globally_primed AND t.current_globally_active < t.max_globally_active;
	IF NOT FOUND THEN
		RETURN;
	END IF;

	UPDATE spicefield_server_availability
	SET requested_spawned_of_type = requested_spawned_of_type + 1
	WHERE server_id = in_server_id AND spicefield_type_id = in_spicefield_type_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58528
f
reset_all_players_from_server_ids_grace_period_and_logoff_timer
in_server_id text, in_reset_time timestamp without time zone
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.reset_all_players_from_server_ids_grace_period_and_logoff_timer(in_server_id text, in_reset_time timestamp without time zone)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE encrypted_player_state SET reconnect_grace_period_end = in_reset_time WHERE server_id = in_server_id AND reconnect_grace_period_end > in_reset_time;
    UPDATE encrypted_player_state SET logoff_persistence_end_time = in_reset_time WHERE server_id = in_server_id AND logoff_persistence_end_time > in_reset_time;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58529
f
reset_global_spice_field_state
in_map_name text, in_dimension_index integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.reset_global_spice_field_state(in_map_name text, in_dimension_index integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE spicefield_server_availability sa
	SET requested_spawned_of_type = 0
	FROM spicefield_types st
	WHERE sa.spicefield_type_id = st.spicefield_type_id AND st.map_name = in_map_name AND st.dimension_index = in_dimension_index;

	UPDATE spicefield_types
	SET current_globally_primed = 0, current_globally_active = 0
	WHERE map_name = in_map_name AND dimension_index = in_dimension_index;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58530
f
reset_journey_story_nodes_for_player
in_player_id text, in_story_node_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.reset_journey_story_nodes_for_player(in_player_id text, in_story_node_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF NOT is_player_offline(in_player_id) THEN
		RAISE EXCEPTION 'Cannot execute query because the player is online - they must be offline in order for the journey data to be updated correctly without risking it being overwritten by player actions.';
	END IF;

	UPDATE journey_story_node
	SET complete_condition_state = jsonb_object(ARRAY[]::text[])
	WHERE story_node_id = ANY(in_story_node_ids)
	AND account_id IN (
		SELECT id
		FROM accounts a
		WHERE a.user = in_player_id
	);

	DELETE FROM journey_story_node_cooldown
	WHERE story_node_id = ANY(in_story_node_ids)
	AND account_id IN (
		SELECT id
		FROM accounts a
		WHERE a.user = in_player_id
	);
END $function$

@@@ENDRTN@@@
@@@RTN@@@58531
f
reset_server_all_player_access_codes
in_account_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.reset_server_all_player_access_codes(in_account_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM player_access_codes
		WHERE account_id = in_account_id
		AND is_resettable = true;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58532
f
reset_specialization_keystones
in_player_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.reset_specialization_keystones(in_player_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM purchased_specialization_keystones WHERE player_id = in_player_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58533
f
reset_specialization_tracks
in_player_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.reset_specialization_tracks(in_player_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM specialization_tracks WHERE player_id = in_player_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58534
f
restore_backup_vehicle
in_account_id bigint, in_server_info dune.serverinfo, in_transform dune.transform
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.restore_backup_vehicle(in_account_id bigint, in_server_info dune.serverinfo, in_transform dune.transform)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
    found_vehicle_id BIGINT;
BEGIN
    -- Retrieve a backup vehicle for the provided account.
    SELECT vehicle_id INTO found_vehicle_id
      FROM backup_vehicles
     WHERE account_id = in_account_id
     LIMIT 1;  -- In case more than one is stored.

    IF NOT FOUND THEN
        RAISE EXCEPTION 'No backup vehicle found for account_id: %', in_account_id;
    END IF;

    -- Only restore vehicle if the actor state is vehicle backup
    PERFORM 1 FROM actor_state WHERE actor_id = found_vehicle_id AND state = 'VehicleBackup';
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Trying to restore vehicle % that does not belong to vehicle backup feature.', found_vehicle_id;
    END IF;

	-- Update the actors record to restore the vehicle.
    UPDATE actors
	SET map = in_server_info.map,
		partition_id = in_server_info.partition_id,
		dimension_index = in_server_info.dimension_index,
		transform = in_transform
	WHERE id = found_vehicle_id;
    IF NOT FOUND THEN
        RAISE WARNING 'No actor record found with id % during restore.', found_vehicle_id;
    END IF;

    -- Remove the restored vehicle from backup_vehicles.
    DELETE FROM backup_vehicles WHERE vehicle_id = found_vehicle_id;
    RAISE INFO 'Deleted backup record for vehicle %.', found_vehicle_id;

    -- There is no need to keep the actor state in the table anymore since the vehicle backup was successful data wise
    DELETE FROM actor_state WHERE actor_id = found_vehicle_id;
    RAISE INFO 'Deleted actor state for vehicle %.', found_vehicle_id;

    PERFORM verify_item_dup_backup_tool(in_account_id, found_vehicle_id, 'item_dup_on_restore_vbt');

    RETURN found_vehicle_id;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58535
f
restore_recovered_vehicle
in_account_id bigint, in_vehicle_id bigint, in_server_info dune.serverinfo, in_transform dune.transform, in_restore_time_limit integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.restore_recovered_vehicle(in_account_id bigint, in_vehicle_id bigint, in_server_info dune.serverinfo, in_transform dune.transform, in_restore_time_limit integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    found_vehicle_id BIGINT;
	found_vehicle_name TEXT;
	found_player_id BIGINT;
BEGIN
    -- Retrieve a recovered vehicle for the provided account.
    SELECT vehicle_id, vehicle_name INTO found_vehicle_id, found_vehicle_name 
    FROM recovered_vehicles
    WHERE account_id = in_account_id AND vehicle_id = in_vehicle_id
	-- 60s leeway in favor of player in case they try to restore last second
	AND (migrated = TRUE OR time_stored > NOW() - (in_restore_time_limit + 60) * INTERVAL '1 second')
    LIMIT 1;  -- In case more than one is stored.

    IF NOT FOUND THEN
        RAISE EXCEPTION 'No recovered vehicle found for account_id: % and vehicle_id: %', in_account_id, in_vehicle_id;
    END IF;

    -- Only restore vehicle if the actor state is VehicleRecovery
    PERFORM 1 FROM actor_state WHERE actor_id = found_vehicle_id AND state = 'VehicleRecovery';
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Trying to restore vehicle % that does not belong to vehicle recovery feature.', found_vehicle_id;
    END IF;

	-- Update the actors record to restore the vehicle.
    UPDATE actors
	SET map = in_server_info.map,
		partition_id = in_server_info.partition_id,
		dimension_index = in_server_info.dimension_index,
		transform = in_transform
	WHERE id = found_vehicle_id;
    IF NOT FOUND THEN
        RAISE WARNING 'No actor record found with id % during restore.', found_vehicle_id;
    END IF;

	-- permissions use the player controllers id, so we need to get that from the account id
	SELECT player_controller_id INTO found_player_id FROM player_state WHERE account_id = in_account_id LIMIT 1;
	IF NOT FOUND THEN
		RAISE EXCEPTION 'No player_controller_id found for account: %', in_account_id;
	END IF;
	
	-- restore default permissions
	INSERT INTO	permission_actor("actor_id", "actor_name", "actor_type", "access_level", "is_child")
	VALUES(found_vehicle_id, found_vehicle_name, 2, 3, false);

	INSERT INTO permission_actor_rank("permission_actor_id", "player_id", "rank")
	VALUES(found_vehicle_id, found_player_id, 1);

    -- Remove the restored vehicle from recovered_vehicles.
    DELETE FROM recovered_vehicles WHERE vehicle_id = found_vehicle_id;
    RAISE INFO 'Deleted recovery record for vehicle %.', found_vehicle_id;

    -- There is no need to keep the actor state in the table anymore since the vehicle recovery was successful data wise
    DELETE FROM actor_state WHERE actor_id = found_vehicle_id;
    RAISE INFO 'Deleted actor state for vehicle %.', found_vehicle_id;

    --PERFORM verify_item_dup_backup_tool(in_account_id, found_vehicle_id, 'item_dup_on_restore_vbt');

END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58536
f
retrieve_all_static_shifting_sand

TABLE(out_id text, out_alpha double precision, out_x double precision, out_y double precision, out_last_modified_time bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.retrieve_all_static_shifting_sand()
 RETURNS TABLE(out_id text, out_alpha double precision, out_x double precision, out_y double precision, out_last_modified_time bigint)
 LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN QUERY
        SELECT id, alpha, x, y, EXTRACT(EPOCH FROM last_modified_time)::BIGINT AS last_modified_time FROM shiftingsands_data;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58537
f
returning_player_award_given
in_account_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.returning_player_award_given(in_account_id bigint)
 RETURNS void
 LANGUAGE sql
AS $function$
    UPDATE player_state SET last_returning_player_awarded_time=now(), last_returning_player_event_time=NULL WHERE account_id=in_account_id;
$function$

@@@ENDRTN@@@
@@@RTN@@@58538
f
reveal_journey_story_nodes_for_player
in_player_id text, in_story_node_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.reveal_journey_story_nodes_for_player(in_player_id text, in_story_node_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF NOT is_player_offline(in_player_id) THEN
		RAISE EXCEPTION 'Cannot execute query because the player is online - they must be offline in order for the journey data to be updated correctly without risking it being overwritten by player actions.';
	END IF;

	WITH player_account_id AS (
		SELECT id
		FROM accounts a
		WHERE a.user = in_player_id
	)
	INSERT INTO journey_story_node(account_id, story_node_id, override_reward_block, has_pending_reward, complete_condition_state, reveal_condition_state, fail_condition_state, metadata_state, reset_group)
	SELECT player_account_id.id, completed_node.story_node_id, completed_node.override_reward_block, completed_node.has_pending_reward, completed_node.complete_condition_state, completed_node.reveal_condition_state, completed_node.fail_condition_state, completed_node.metadata_state, completed_node.reset_group
	FROM player_account_id 
	CROSS JOIN (
		SELECT story_node_id, false, false, jsonb_object(ARRAY[]::text[]), to_jsonb(true), jsonb_object(ARRAY[]::text[]), jsonb_object(ARRAY[]::text[]), 'Default'::JourneyStoryResetGroup
		FROM UNNEST(in_story_node_ids) AS story_node_id
	) completed_node(story_node_id, override_reward_block, has_pending_reward, complete_condition_state, reveal_condition_state, fail_condition_state, metadata_state, reset_group)
	ON CONFLICT ON CONSTRAINT journey_story_node_pkey
		DO UPDATE SET
			reveal_condition_state = EXCLUDED.reveal_condition_state,
			metadata_state = EXCLUDED.metadata_state;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58539
f
save_aborted_authority_transfer_actors
in_actor_ids bigint[], in_partition_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_aborted_authority_transfer_actors(in_actor_ids bigint[], in_partition_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO actor_state(actor_id, state)
        SELECT a.id, 'AbortedAuthorityTransfer'
        FROM actors AS a
        WHERE a.id = ANY(in_actor_ids) AND a.partition_id = in_partition_id
    ON CONFLICT DO NOTHING;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58540
f
save_actor_dislocation
in_actor_id bigint, in_current_server_info dune.serverinfo, in_target_location dune.vector, in_target_dimension_index integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_actor_dislocation(in_actor_id bigint, in_current_server_info dune.serverinfo, in_target_location dune.vector, in_target_dimension_index integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	update actors
		set
			transform = (in_target_location, (transform).rotation),
			dimension_index = in_target_dimension_index,
			partition_id = null
		where id = in_actor_id
			and map = (in_current_server_info).map
			and dimension_index = (in_current_server_info).dimension_index
			and (
				partition_id is null
				or (in_current_server_info).partition_id is null
				or partition_id = (in_current_server_info).partition_id
			);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58541
f
save_actors
in_server_info dune.serverinfo, in_actors dune.actordescription[], in_actor_state dune.actorstate
TABLE(actor_id bigint, current_saved_serial bigint, saved boolean)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_actors(in_server_info dune.serverinfo, in_actors dune.actordescription[], in_actor_state dune.actorstate DEFAULT 'Default'::dune.actorstate)
 RETURNS TABLE(actor_id bigint, current_saved_serial bigint, saved boolean)
 LANGUAGE plpgsql
AS $function$
BEGIN
	return query with
		input_actors as (
			select * from unnest(in_actors)
		),
		valid_input_actors as (
			select input_actors.id from input_actors
			left join actor_state on input_actors.id = actor_state.actor_id
			where actor_state.state = in_actor_state or (in_actor_state = 'Default' and actor_state.actor_id is null)
		),
		serial_checks as (
			select
				input.id,
				input.serial as input_serial,
				coalesce(actors.serial, 0) as saved_serial,
				input.serial >= coalesce(actors.serial, 0) and input.id in (select id from valid_input_actors) as should_save
			from
				input_actors as input left join actors using (id)
		),
		actors_to_save as (
			select i.* from input_actors as i join serial_checks as c using (id) where c.should_save
		),
		upsert_actors as (
			insert into actors(
				"id", "class", "transform",
				"gas_attributes",
				"properties",
				"map", "partition_id", "dimension_index",
				"serial"
			)
				select
					i.id, i.class_name, i.transform,
					(i.generic_data).gas_attribute_sets_json, (i.generic_data).properties_json,
					in_server_info.map, in_server_info.partition_id, coalesce(in_server_info.dimension_index, 0),
					i.serial
				from actors_to_save as i
			on conflict (id) do update
			set
				"class" = EXCLUDED.class, "transform" = case when EXCLUDED.transform is null or (EXCLUDED.transform).location = (zero_transform()).location then actors.transform else EXCLUDED.transform end,

				"gas_attributes" = EXCLUDED.gas_attributes, "properties" = EXCLUDED.properties,

				"map" = EXCLUDED.map, "partition_id" = EXCLUDED.partition_id, "dimension_index" = EXCLUDED.dimension_index,
				"serial" = EXCLUDED.serial
			returning id
		),
		fgl_entity_data as (
			select id as actor_id, (u).entity_id, (u).slot_name, (u).components_json as components
				from (select id, unnest((generic_data).entities) as u from actors_to_save) q
		),
		missing_entities as (
			select entity_id
				from actors_to_save join actor_fgl_entities as existing on (actors_to_save.id = existing.actor_id)
				where not exists(select 1 from fgl_entity_data as updated where updated.entity_id=existing.entity_id)
		),
		delete_missing_entity_links as (
			delete from actor_fgl_entities as existing using missing_entities
				where existing.entity_id=missing_entities.entity_id
				returning existing.entity_id as deleted_entity_id
		),
		delete_missing_entities as (
			delete from fgl_entities as existing using missing_entities
				where existing.entity_id=missing_entities.entity_id
		),
		upsert_entities as (
			insert into fgl_entities("entity_id", "components")
				select entity_id, components from fgl_entity_data
				on conflict (entity_id) do update
				set components=EXCLUDED.components
			returning entity_id
		),
		upsert_entity_links as (
			insert into actor_fgl_entities("actor_id", "entity_id", "slot_name")
				select fgl_entity_data.actor_id, fgl_entity_data.entity_id, fgl_entity_data.slot_name
					from fgl_entity_data left join upsert_entities using (entity_id)
					-- HACK: this is a temporary fix until we do TECH-23063
					where not entity_id in (select deleted_entity_id from delete_missing_entity_links)
				on conflict (entity_id) do update
				set
					actor_id=EXCLUDED.actor_id,
					slot_name=EXCLUDED.slot_name
			returning actor_fgl_entities.actor_id, actor_fgl_entities.entity_id
		),
		all_actor_entities as (
			select fgl_entity_data.actor_id as id, array_agg(entity_id) as entity_ids
				from fgl_entity_data
					left join upsert_entities using (entity_id)
					left join upsert_entity_links using (entity_id)
				group by fgl_entity_data.actor_id
		),
		extra_data as (
			select
				input_actors.id,
				serial_checks.saved_serial,
				(serial_checks.should_save) as saved,
				(input_actors.generic_data).building_actor_data as building_data,
				(input_actors.generic_data).placeable_actor_data as placeable_data,
				(input_actors.generic_data).totem_actor_data as totem_data,
				coalesce(all_actor_entities.entity_ids, array[]::int[]) as entity_ids
			from
				serial_checks
				left join input_actors using(id)
				left join upsert_actors using(id) -- this is needed for dependency
				left join all_actor_entities using(id) -- this is needed for dependency only
		),
		save_extras as (
			select
				extra_data.id,
				extra_data.saved_serial,
				extra_data.saved,
				case when extra_data.saved and building_data is not null then
					save_building(id, building_data)
				end,
				case when extra_data.saved and placeable_data is not null then
					save_placeable(id, placeable_data)
				end,
				case when extra_data.saved and totem_data is not null then
					save_totem(id, totem_data)
				end,
				entity_ids
			from extra_data
		)
		select id, saved_serial, save_extras.saved from save_extras;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58543
f
save_building
in_building_id bigint, in_data dune.buildingsavedata
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_building(in_building_id bigint, in_data dune.buildingsavedata)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	instance BUILDINGINSTANCE;
	instance_to_remove INTEGER;
	instance_owner BuildingInstanceUpdateOwner;
BEGIN
	-- ADD
	IF array_length(in_data.in_add_building_data, 1) > 0 THEN
		INSERT INTO building_instances(
			"building_id",
			"instance_id",
			"building_type",
			"transform",
			"owner_entity_id",
			"building_flags",
			"health",
			"shelter",
			"stabilization_begin_timespan",
			"stabilization_end_timespan",
			"stabilization_state",
			"sand_buildup"
		)
		SELECT
			in_building_id,
			add_data.instance_id,
			add_data.building_type,
			add_data.transform,
			_building_validate_totem_owner_id(add_data.owner_entity_id),
			add_data.building_flags,
			add_data.health,
			add_data.shelter,
			add_data.stabilization_begin_timespan,
			add_data.stabilization_end_timespan,
			add_data.stabilization_state,
			add_data.sand_buildup
		FROM unnest(in_data.in_add_building_data) as add_data
		ON CONFLICT ("building_id", "instance_id")
			DO UPDATE SET
				"building_type" = (instance).building_type,
				"transform" = (instance).transform,
				"owner_entity_id" = _building_validate_totem_owner_id((instance).owner_entity_id),
				"building_flags" = (instance).building_flags,
				"health" = (instance).health,
				"shelter" = (instance).shelter,
				"stabilization_begin_timespan" = (instance).stabilization_begin_timespan,
				"stabilization_end_timespan" = (instance).stabilization_end_timespan,
				"stabilization_state" = (instance).stabilization_state,
				"sand_buildup" = (instance).sand_buildup;
	END IF;

	-- REMOVE
	IF array_length(in_data.in_remove_building_data, 1) > 0 THEN
		DELETE FROM building_instances
			WHERE building_instances."building_id" = in_building_id AND building_instances."instance_id" = ANY(in_data.in_remove_building_data);
	END IF;

	-- OWNER. 99.99% of the time, this will only have 1 Owner Array.
	IF array_length(in_data.in_building_owner_data, 1) > 0 THEN
		FOREACH instance_owner IN ARRAY in_data.in_building_owner_data LOOP
			WITH owner_changes_table AS
			(
				SELECT unnest(instance_owner.instances) AS instance_id, instance_owner.owner_entity_id AS owner_entity_id
			)
			UPDATE building_instances
				SET owner_entity_id = _building_validate_totem_owner_id(owner_changes_table.owner_entity_id)
			FROM owner_changes_table
			WHERE building_instances.building_id = in_building_id AND building_instances.instance_id = owner_changes_table.instance_id;
		END LOOP;
	END IF;

	-- STABILIZATION
	IF array_length(in_data.in_building_stabilization_data, 1) > 0 THEN
		WITH stabilization_changes_table AS
		(
			select * FROM unnest(in_data.in_building_stabilization_data)
		)
		UPDATE building_instances SET stabilization_begin_timespan = stabilization_changes_table.stabilization_begin_timespan, stabilization_end_timespan = stabilization_changes_table.stabilization_end_timespan, stabilization_state = stabilization_changes_table.stabilization_state
		FROM stabilization_changes_table
		WHERE building_instances.building_id = in_building_id AND building_instances.instance_id = stabilization_changes_table.instance_id;
	END IF;

	-- HEALTH
	IF array_length(in_data.in_building_health_data, 1) > 0 THEN
		WITH health_changes_table AS
		(
			select * FROM unnest(in_data.in_building_health_data)
		)
		UPDATE building_instances SET health = health_changes_table.health
		FROM health_changes_table
		WHERE building_instances.building_id = in_building_id AND building_instances.instance_id = health_changes_table.instance_id;
	END IF;

	-- SHELTER
	IF array_length(in_data.in_building_shelter_data, 1) > 0 THEN
		WITH shelter_changes_table AS
		(
			select * FROM unnest(in_data.in_building_shelter_data)
		)
		UPDATE building_instances SET shelter = shelter_changes_table.shelter
		FROM shelter_changes_table
		WHERE building_instances.building_id = in_building_id AND building_instances.instance_id = shelter_changes_table.instance_id;
	END IF;

	-- SAND BUILDUP
	IF array_length(in_data.in_building_sand_buildup_data, 1) > 0 THEN
		WITH sand_buildup_changes_table AS
		(
			select * FROM unnest(in_data.in_building_sand_buildup_data)
		)
		UPDATE building_instances SET sand_buildup = sand_buildup_changes_table.sand_buildup
		FROM sand_buildup_changes_table
		WHERE building_instances.building_id = in_building_id AND building_instances.instance_id = sand_buildup_changes_table.instance_id;
	END IF;

	-- BUILDING FLAGS
	IF array_length(in_data.in_building_building_flags_data, 1) > 0 THEN
		WITH building_flags_changes_table AS
		(
			select * FROM unnest(in_data.in_building_building_flags_data)
		)
		UPDATE building_instances SET building_flags = building_flags_changes_table.building_flags
		FROM building_flags_changes_table
		WHERE building_instances.building_id = in_building_id AND building_instances.instance_id = building_flags_changes_table.instance_id;
	END IF;

	-- BUILDING TRANSFORM
	IF array_length(in_data.in_building_building_transform_data, 1) > 0 THEN
		WITH building_flags_transform_table AS
		(
			select * FROM unnest(in_data.in_building_building_transform_data)
		)
		UPDATE building_instances SET transform = building_flags_transform_table.transform
		FROM building_flags_transform_table
		WHERE building_instances.building_id = in_building_id AND building_instances.instance_id = building_flags_transform_table.instance_id;
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58544
f
save_building_blueprint_copy
in_building_item_id bigint, in_building_blueprint_id bigint, in_building_blueprint_building_data dune.buildingblueprintpiecesaveitemcontainer[], in_building_blueprint_placeable_data dune.buildingblueprintplaceablesaveitemcontainer[], in_building_blueprint_pentashield_data dune.buildingblueprintpentashielditem[]
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_building_blueprint_copy(in_building_item_id bigint, in_building_blueprint_id bigint, in_building_blueprint_building_data dune.buildingblueprintpiecesaveitemcontainer[], in_building_blueprint_placeable_data dune.buildingblueprintplaceablesaveitemcontainer[], in_building_blueprint_pentashield_data dune.buildingblueprintpentashielditem[])
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	return_id BIGINT;
BEGIN
	IF in_building_blueprint_id != 0 THEN
		DELETE FROM building_blueprints WHERE id = in_building_blueprint_id;
    END IF;

    INSERT INTO building_blueprints(id, item_id, player_id, building_blueprint_map)
        VALUES(DEFAULT, in_building_item_id, NULL, '') RETURNING id INTO return_id;

 -- All Building Pieces
    INSERT INTO building_blueprint_instances(building_blueprint_id, instance_id, building_type, transform, provides_stability, health, hologram)
    SELECT
        return_id,
        piece_data.instance_id,
        container_data.building_type,
        piece_data.transform,
        piece_data.provides_stability,
        piece_data.health,
        True
    FROM
        unnest(in_building_blueprint_building_data) AS container_data
        CROSS JOIN LATERAL unnest(container_data.building_pieces) AS piece_data;

-- All Placeables
    INSERT INTO building_blueprint_placeables(building_blueprint_id, placeable_id, building_type, transform, hologram)
    SELECT
        return_id,
        placeable_data.placeable_id,
        container_data.building_type,
        placeable_data.transform,
        True
    FROM
        unnest(in_building_blueprint_placeable_data) AS container_data
        CROSS JOIN LATERAL unnest(container_data.placeables) AS placeable_data;

-- Pentashields
    INSERT INTO building_blueprint_pentashields("building_blueprint_id", "placeable_id", "scale")
    SELECT return_id as building_blueprint_id, placeable_id, scale FROM unnest(in_building_blueprint_pentashield_data);

    RETURN return_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58545
f
save_demo_account_time
in_fls_id text, in_demo_playtime_seconds integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_demo_account_time(in_fls_id text, in_demo_playtime_seconds integer)
 RETURNS void
 LANGUAGE sql
AS $function$
	INSERT INTO demo_users("fls_id", "demo_playtime_seconds", "demo_state")
	VALUES (in_fls_id, in_demo_playtime_seconds, 'Demo'::DemoState)
	ON CONFLICT ("fls_id") DO UPDATE
	SET demo_playtime_seconds = EXCLUDED.demo_playtime_seconds; 
$function$

@@@ENDRTN@@@
@@@RTN@@@58546
f
save_dialogue_data
in_player_controller_id bigint, in_met_npcs text[], in_taken_nodes integer[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_dialogue_data(in_player_controller_id bigint, in_met_npcs text[], in_taken_nodes integer[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO dialogue_met_npcs(player_id, npc_name) SELECT in_player_controller_id, unnest(in_met_npcs)
    ON CONFLICT DO NOTHING;

    INSERT INTO dialogue_taken_nodes(player_id, node_id) SELECT in_player_controller_id, unnest(in_taken_nodes)
    ON CONFLICT DO NOTHING;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58547
f
save_item
in_item dune.inventoryitem
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_item(in_item dune.inventoryitem)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	INSERT INTO
		items (id, inventory_id, stack_size, quality_level, volume_override, position_index, template_id, stats, is_new, acquisition_time)
		VALUES (
			(in_item).item_id,
			(in_item).inventory_id,
			(in_item).stack_size,
			(in_item).quality_level,
            (in_item).volume_override,
			(in_item).position_index,
			(in_item).template_id,
			(in_item).stats,
			(in_item).is_new,
			(in_item).acquisition_time
		)
		ON CONFLICT (id)
			DO UPDATE SET
				inventory_id = (in_item).inventory_id,
				stack_size = (in_item).stack_size,
                quality_level = (in_item).quality_level,
                volume_override = (in_item).volume_override,
				position_index = (in_item).position_index,
				template_id = (in_item).template_id,
				stats = items.stats || (in_item).stats,
				is_new = (in_item).is_new,
				acquisition_time = (in_item).acquisition_time;
    
    -- log item tracking
    PERFORM _add_item_trace_log('save_item', (in_item).item_id, (in_item).inventory_id, (in_item).template_id, (in_item).position_index);
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58548
f
save_journey_story_node
in_account_id bigint, in_story_node_id text, in_override_reward_block boolean, in_has_pending_reward boolean, in_complete_condition_state jsonb, in_reveal_condition_state jsonb, in_fail_condition_state jsonb, in_metadata_state jsonb, in_reset_group dune.journeystoryresetgroup
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_journey_story_node(in_account_id bigint, in_story_node_id text, in_override_reward_block boolean, in_has_pending_reward boolean, in_complete_condition_state jsonb, in_reveal_condition_state jsonb, in_fail_condition_state jsonb, in_metadata_state jsonb, in_reset_group dune.journeystoryresetgroup)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO journey_story_node(account_id, story_node_id, override_reward_block, has_pending_reward, complete_condition_state, reveal_condition_state, fail_condition_state, metadata_state, reset_group)
	VALUES(in_account_id, in_story_node_id, in_override_reward_block, in_has_pending_reward, in_complete_condition_state, in_reveal_condition_state, in_fail_condition_state, in_metadata_state, in_reset_group)
	ON CONFLICT (account_id, story_node_id)
	DO UPDATE SET
        override_reward_block = in_override_reward_block,
        has_pending_reward = in_has_pending_reward,
        complete_condition_state = in_complete_condition_state,
        reveal_condition_state = in_reveal_condition_state,
		fail_condition_state = in_fail_condition_state,
		metadata_state = in_metadata_state,
		reset_group = in_reset_group;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58549
f
save_journey_story_nodes
in_account_id bigint, in_journey_data dune.savejourneydata[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_journey_story_nodes(in_account_id bigint, in_journey_data dune.savejourneydata[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO journey_story_node(account_id, story_node_id, override_reward_block, has_pending_reward, complete_condition_state, reveal_condition_state, fail_condition_state, metadata_state, reset_group)
		SELECT in_account_id, story_node_id, override_reward_block, has_pending_reward, to_jsonb(completion_state_string), to_jsonb(reveal_state_string), to_jsonb(fail_state_string), to_jsonb(metadata_state_string), reset_group
		FROM UNNEST(in_journey_data)
	ON CONFLICT ON CONSTRAINT journey_story_node_pkey
		DO UPDATE SET
			override_reward_block = EXCLUDED.override_reward_block,
            has_pending_reward = EXCLUDED.has_pending_reward,
			complete_condition_state = EXCLUDED.complete_condition_state,
			reveal_condition_state = EXCLUDED.reveal_condition_state,
			fail_condition_state = EXCLUDED.fail_condition_state,
			metadata_state = EXCLUDED.metadata_state,
			reset_group = EXCLUDED.reset_group;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58550
f
save_login_target_dimension
in_fls_id text, in_login_target_dimension_index integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_login_target_dimension(in_fls_id text, in_login_target_dimension_index integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO player_travel_state (fls_id, login_target_dimension_index)
	VALUES (in_fls_id, in_login_target_dimension_index)
	ON CONFLICT (fls_id) DO UPDATE
	SET login_target_dimension_index = EXCLUDED.login_target_dimension_index;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58551
f
save_markers
in_player_marker_data dune.saveplayermarkerdata[], in_marker_data dune.savemarkerdata[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_markers(in_player_marker_data dune.saveplayermarkerdata[], in_marker_data dune.savemarkerdata[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	null_player_marker_map_id_count INTEGER;
	null_marker_map_id_count INTEGER;
BEGIN

	SELECT COUNT(*)
    INTO null_player_marker_map_id_count
	FROM UNNEST(in_player_marker_data) AS umd
	LEFT JOIN map_names AS mn ON umd.map_name = mn.map_name
    WHERE mn.map_name_id IS NULL; -- Find map name that doesn't exist

	IF null_player_marker_map_id_count > 0 THEN
        RAISE EXCEPTION 'Found records with NULL map name id for player markers!';
    END IF;

    SELECT COUNT(*)
    INTO null_marker_map_id_count
	FROM UNNEST(in_marker_data) AS umd
	LEFT JOIN map_names AS mn ON umd.map_name = mn.map_name
    WHERE mn.map_name_id IS NULL; -- Find map name that doesn't exist

	IF null_marker_map_id_count > 0 THEN
        RAISE EXCEPTION 'Found records with NULL map name id for markers!';
    END IF;

    INSERT INTO markers("marker_hash_id", "dimension_index", "map_name_id", "marker", "area_id", "area_radius", "long_range", "payload")
        SELECT
           	umd.marker_hash_id,
            umd.dimension_index,
            mn.map_name_id,
            umd.marker,
            umd.area_id,
            umd.area_radius,
            umd.long_range,
            umd.payload
        FROM UNNEST(in_marker_data) umd JOIN map_names mn USING(map_name)
    	ORDER BY marker_hash_id, dimension_index, map_name_id
    ON CONFLICT ON CONSTRAINT markers_pkey
        DO UPDATE SET
            "area_id" = EXCLUDED.area_id,
            "area_radius" = EXCLUDED.area_radius,
            "long_range" = EXCLUDED.long_range,
            "payload" = EXCLUDED.payload;

	WITH marker_data_for_existing_players AS (
    	SELECT
			player_data.player_id,
			player_data.marker_hash_id,
			player_data.dimension_index,
			mn.map_name_id,
			player_data.discovery_level,
			player_data.discovery_method,
			player_data.player_payload
    	FROM UNNEST(in_player_marker_data) AS player_data
    	JOIN actors ON actors.id = player_data.player_id
    	JOIN map_names mn USING(map_name)
	)
	INSERT INTO player_markers("player_id", "marker_hash_id", "dimension_index", "map_name_id", "discovery_level", "discovery_method", "payload")
		SELECT
			in_data.player_id,
			in_data.marker_hash_id,
			in_data.dimension_index,
			in_data.map_name_id,
			in_data.discovery_level,
			in_data.discovery_method,
			in_data.player_payload
		FROM marker_data_for_existing_players AS in_data, markers
		WHERE markers.marker_hash_id = in_data.marker_hash_id AND markers.dimension_index = in_data.dimension_index AND markers.map_name_id = in_data.map_name_id
		ORDER BY player_id, in_data.marker_hash_id, in_data.dimension_index, in_data.map_name_id -- Ordering for deadlock avoidance
	ON CONFLICT ON CONSTRAINT player_markers_pkey
		DO UPDATE SET
			"discovery_level" = EXCLUDED.discovery_level,
			"discovery_method" = EXCLUDED.discovery_method,
			"payload" = EXCLUDED.payload;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58552
f
save_mnemonic_recall_lesson
in_account_id bigint, in_lesson_id text, in_lesson_state bigint, in_lesson_progress integer, in_is_new boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_mnemonic_recall_lesson(in_account_id bigint, in_lesson_id text, in_lesson_state bigint, in_lesson_progress integer, in_is_new boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO mnemonic_recall(account_id, lesson_id, lesson_state, lesson_progress, is_new)
    VALUES(in_account_id, in_lesson_id, in_lesson_state, in_lesson_progress, in_is_new)
    ON CONFLICT (account_id, lesson_id)
    DO UPDATE SET
        lesson_state = in_lesson_state,
        lesson_progress = in_lesson_progress,
        is_new = in_is_new;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58561
f
save_placeable
in_placeable_id bigint, in_data dune.placeablesavedata
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_placeable(in_placeable_id bigint, in_data dune.placeablesavedata)
 RETURNS void
 LANGUAGE sql
BEGIN ATOMIC
 INSERT INTO dune.placeables (id, owner_entity_id, health, building_type, has_hit_ground, has_buildable_support, is_hologram)
   VALUES (save_placeable.in_placeable_id, dune._placeable_validate_totem_owner_id((save_placeable.in_data).in_owner_entity_id), (save_placeable.in_data).in_health, (save_placeable.in_data).in_building_type, (save_placeable.in_data).in_has_hit_ground, (save_placeable.in_data).in_has_buildable_support, (save_placeable.in_data).in_is_hologram) ON CONFLICT(id) DO UPDATE SET owner_entity_id = excluded.owner_entity_id, health = excluded.health, building_type = excluded.building_type, has_hit_ground = excluded.has_hit_ground, has_buildable_support = excluded.has_buildable_support, is_hologram = excluded.is_hologram;
END

@@@ENDRTN@@@
@@@RTN@@@58562
f
save_player
in_player dune.playerdescription
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_player(in_player dune.playerdescription)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
DECLARE
    fgl_entity FglEntity;
    player_actors ActorDescription[];
    all_serials_are_same Boolean;
    should_save_all_actors Boolean;
BEGIN
    player_actors := array[in_player.state, in_player.controller];

    IF NOT in_player.pawn IS NULL THEN
        foreach fgl_entity in array (in_player).pawn.generic_data.entities loop
            if fgl_entity.slot_name in ('DuneCharacter', 'PersonalCrafting') and fgl_entity.components_json = '{}'::jsonb then
                raise exception 'invalid player save with empty fgl state for pawn';
            end if;
        end loop;
        player_actors := player_actors || in_player.pawn;
    END IF;

    with
        ids_and_serials as (
            select
                input.serial = (in_player.controller).serial as is_matching_serial,
                input.serial >= existing.serial as should_save
            from unnest(player_actors) as input join actors as existing using (id)
        )
        select bool_and(is_matching_serial), bool_and(should_save) from ids_and_serials
        into all_serials_are_same, should_save_all_actors;

    IF NOT all_serials_are_same THEN
        raise exception 'serial: serial mismatch between the player actors';
    end if;

    -- Demo time remaining
    IF in_player.demo_playtime_seconds IS NOT NULL THEN
        UPDATE demo_users
        SET demo_playtime_seconds = in_player.demo_playtime_seconds
        WHERE fls_id = (
            SELECT acc.user FROM accounts AS acc
            WHERE acc.id = in_player.id
        );
    END IF;

    -- Demo state
    IF in_player.demo_state IS NOT NULL THEN
        UPDATE demo_users
		SET demo_state = in_player.demo_state
		WHERE fls_id = (
            SELECT acc.user FROM accounts AS acc
            WHERE acc.id = in_player.id
        );
    END IF;

    IF NOT should_save_all_actors THEN
        return false;
    end if;

    -- Allow saving of the player without having a player character (pawn)
    PERFORM save_actors(in_player.pawn_server_info, player_actors);

    PERFORM update_respawn_locations(in_player.id, (in_player.respawn_info).locations);

    PERFORM update_death_location(in_player.pawn, in_player.pawn_server_info, in_player.life_state);

    UPDATE player_state
        SET pending_respawn_location_id=(in_player.respawn_info).pending_location_id, life_state=in_player.life_state
        WHERE account_id=in_player.id;

    return true;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58563
f
save_player_pawn
in_pawn dune.actordescription, in_server_info dune.serverinfo, in_life_state dune.playerlifestate
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_player_pawn(in_pawn dune.actordescription, in_server_info dune.serverinfo, in_life_state dune.playerlifestate)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
DECLARE
    should_save_pawn BOOLEAN;
BEGIN
    WITH
        ids_and_serials AS (
            SELECT actors.serial >= (in_pawn).serial AS should_save
            FROM actors
            WHERE actors.id = in_pawn.id
        )
        SELECT should_save FROM ids_and_serials
        INTO should_save_pawn;

    IF NOT should_save_pawn THEN
        RETURN false;
    END IF;

    PERFORM save_actors(in_server_info, ARRAY[in_pawn]);

    PERFORM update_death_location(in_pawn, in_server_info, in_life_state);

    RETURN true;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58564
f
save_static_encounter_name
in_map_name text, in_package_name text, in_actor_name text, in_encounter_name text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_static_encounter_name(in_map_name text, in_package_name text, in_actor_name text, in_encounter_name text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO encounters_static(map_name, package_name, actor_name, encounter_name, waiting_for_reset) 
    VALUES(in_map_name, in_package_name, in_actor_name, in_encounter_name, false) 
    ON CONFLICT(map_name, package_name, actor_name) 
    DO UPDATE SET encounter_name = in_encounter_name, waiting_for_reset = false 
    WHERE encounters_static.map_name = in_map_name AND encounters_static.package_name = in_package_name AND encounters_static.actor_name = in_actor_name;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58565
f
save_static_encounter_waiting_for_reset
in_map_name text, in_package_name text, in_actor_name text, in_waiting_for_reset boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_static_encounter_waiting_for_reset(in_map_name text, in_package_name text, in_actor_name text, in_waiting_for_reset boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE encounters_static 
    SET waiting_for_reset = in_waiting_for_reset 
    WHERE encounters_static.map_name = in_map_name AND encounters_static.package_name = in_package_name AND encounters_static.actor_name = in_actor_name;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58571
f
save_totem
in_id bigint, in_data dune.totemsavedata
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_totem(in_id bigint, in_data dune.totemsavedata)
 RETURNS void
 LANGUAGE sql
BEGIN ATOMIC
 INSERT INTO dune.totems (id, landclaim_vertical_level, last_backup_timestamp, landclaim_original_global_location, landclaim_original_global_yaw_rotation)
   VALUES (save_totem.in_id, (save_totem.in_data).landclaim_vertical_level, (save_totem.in_data).last_backup_timestamp, (save_totem.in_data).landclaim_original_global_location, (save_totem.in_data).landclaim_original_global_yaw_rotation) ON CONFLICT(id) DO UPDATE SET landclaim_vertical_level = excluded.landclaim_vertical_level, last_backup_timestamp = excluded.last_backup_timestamp, landclaim_original_global_location = excluded.landclaim_original_global_location, landclaim_original_global_yaw_rotation = excluded.landclaim_original_global_yaw_rotation;
END

@@@ENDRTN@@@
@@@RTN@@@58572
f
save_tracked_journey_cards
in_player_id bigint, in_tracked_journey_card text, in_tracked_landsraad_card text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_tracked_journey_cards(in_player_id bigint, in_tracked_journey_card text, in_tracked_landsraad_card text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO journey_tracked_cards (player_id, tracked_journey_card, tracked_landsraad_card) Values(in_player_id, in_tracked_journey_card, in_tracked_landsraad_card)
	ON CONFLICT(player_id) DO
	UPDATE SET tracked_journey_card = in_tracked_journey_card, tracked_landsraad_card = in_tracked_landsraad_card;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58573
f
save_travel_return_info
in_player_controller_id bigint, in_map text, in_transform dune.transform
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_travel_return_info(in_player_controller_id bigint, in_map text, in_transform dune.transform)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	insert into travel_return_info(
		"player_controller_id", "map", "transform"
	)
	values(
			  in_player_controller_id, in_map, in_transform
	)
	on conflict (player_controller_id) do update
		set
			"map" = EXCLUDED.map,
			"transform" = EXCLUDED.transform;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58574
f
save_vehicle_modules
in_add_list dune.vehiclemodule[], in_delete_list bigint[], in_stat_update dune.itemstatupdate[]
SETOF bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_vehicle_modules(in_add_list dune.vehiclemodule[], in_delete_list bigint[], in_stat_update dune.itemstatupdate[])
 RETURNS SETOF bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	module VEHICLEMODULE;
	stat ItemStatUpdate;
	new_module_id BIGINT;
	currentstat RECORD;
BEGIN
	--RAISE NOTICE 'Add vehicle modules';
	-- add vehicle modules
	FOREACH module IN ARRAY in_add_list LOOP
		INSERT INTO vehicle_modules(
			"vehicle_id", "template_id", "stats"
		) VALUES(
			(module).vehicle_id, (module).template_id, (module).stats
		) RETURNING id INTO new_module_id;
		RETURN NEXT new_module_id;
	END LOOP;

	--RAISE NOTICE 'Delete vehicle modules';
	-- delete modules
	DELETE FROM vehicle_modules WHERE id = ANY(in_delete_list);

	--RAISE NOTICE 'Add vehicle module stats';
	-- add vehicle module stats
	FOREACH stat IN ARRAY in_stat_update LOOP
		UPDATE vehicle_modules SET "stats" = vehicle_modules.stats || (stat).value WHERE "id" = (stat).item_id;
	END LOOP;

	RETURN;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58575
f
save_world_partition
in_map_name text, in_server_id text, in_dimension_index bigint, in_partition_definition jsonb, in_blocked boolean, in_label text
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.save_world_partition(in_map_name text, in_server_id text, in_dimension_index bigint, in_partition_definition jsonb, in_blocked boolean DEFAULT false, in_label text DEFAULT NULL::text)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	partition_id BIGINT;
BEGIN
	LOCK TABLE world_partition; -- only one at a time, please
	INSERT INTO world_partition(partition_id, server_id, map, partition_definition, dimension_index, blocked, label) VALUES(DEFAULT, in_server_id, in_map_name, in_partition_definition, in_dimension_index, in_blocked, in_label)
		ON CONFLICT ("server_id", "map") DO UPDATE set partition_definition = in_partition_definition, blocked = in_blocked, label = in_label WHERE world_partition.server_id = in_server_id
		RETURNING world_partition.partition_id INTO partition_id;
	RETURN partition_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58586
f
server_info_match
in_actor dune.actors, in_server_info dune.serverinfo
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.server_info_match(in_actor dune.actors, in_server_info dune.serverinfo)
 RETURNS boolean
 LANGUAGE plpgsql
 STABLE STRICT
AS $function$
BEGIN
	return in_actor.map = in_server_info.map
        AND in_actor.dimension_index = in_server_info.dimension_index
        AND (
            in_actor.partition_id IS NULL
            OR
            in_server_info.partition_id IS NULL
            OR
            in_actor.partition_id = in_server_info.partition_id
        );
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58587
f
set_account_as_takeoverable
in_user_id text, in_new_user_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.set_account_as_takeoverable(in_user_id text, in_new_user_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE accounts SET "user"=in_new_user_id, takeoverable=TRUE WHERE "user"=in_user_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58588
f
set_all_inactive_players_in_farm_offline

void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.set_all_inactive_players_in_farm_offline()
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE player_state SET online_status = 'Offline', last_avatar_activity = current_timestamp WHERE online_status <> 'Offline' AND server_id NOT IN (SELECT * FROM active_server_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58589
f
set_battlegroup_close_date
in_close_date timestamp without time zone
timestamp without time zone

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.set_battlegroup_close_date(in_close_date timestamp without time zone)
 RETURNS timestamp without time zone
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO farm_variables (one_row, battlegroup_close_date) VALUES (true,in_close_date)
	ON CONFLICT (one_row) DO UPDATE SET battlegroup_close_date = in_close_date;
	RETURN (select * from get_battlegroup_close_date());
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58590
f
set_character_import_state
in_fls_id text, in_state dune.transferimportstate
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.set_character_import_state(in_fls_id text, in_state dune.transferimportstate)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO character_transfer_imports (fls_id, last_update, transfer_state)
	VALUES (in_fls_id, now(), in_state)
	ON CONFLICT (fls_id) DO UPDATE
	SET last_update = now(),
		transfer_state = EXCLUDED.transfer_state;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58591
f
set_character_name
in_account_id bigint, in_name text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.set_character_name(in_account_id bigint, in_name text)
 RETURNS void
 LANGUAGE sql
AS $function$
    update encrypted_player_state set encrypted_character_name=encrypt_user_data(in_name) where account_id=in_account_id;
$function$

@@@ENDRTN@@@
@@@RTN@@@58592
f
set_demo_state
in_user_id text, in_demo_state dune.demostate
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.set_demo_state(in_user_id text, in_demo_state dune.demostate)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	UPDATE demo_users
	SET demo_state = in_demo_state
	WHERE fls_id = in_user_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58593
f
set_item_tracking_enabled
in_enabled boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.set_item_tracking_enabled(in_enabled boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM set_config('dune.item_tracking_enabled', in_enabled::TEXT, false);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58594
f
set_player_faction_reputation
in_actor_id bigint, in_faction_id smallint, in_reputation_amount integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.set_player_faction_reputation(in_actor_id bigint, in_faction_id smallint, in_reputation_amount integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO player_faction_reputation (actor_id, faction_id, reputation_amount)
	VALUES (in_actor_id, in_faction_id, in_reputation_amount)
	ON CONFLICT (actor_id, faction_id)
		DO UPDATE
		SET reputation_amount = EXCLUDED.reputation_amount;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58595
f
set_players_from_server_ids_offline
in_server_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.set_players_from_server_ids_offline(in_server_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE player_state SET online_status = 'Offline', last_avatar_activity = current_timestamp WHERE online_status <> 'Offline' AND server_id = ANY(in_server_ids);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58596
f
set_specialization_xp_and_level
in_player_id bigint, in_track_type dune.specializationtracktype, in_xp_amount integer, in_level real
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.set_specialization_xp_and_level(in_player_id bigint, in_track_type dune.specializationtracktype, in_xp_amount integer, in_level real)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO specialization_tracks (player_id, track_type, xp_amount, level) VALUES (in_player_id, in_track_type, in_xp_amount, in_level)
	ON CONFLICT(player_id, track_type) DO UPDATE SET xp_amount = in_xp_amount, level = in_level;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58597
p
setup_user_data_encryption
IN in_enable boolean


@@@BODY@@@
CREATE OR REPLACE PROCEDURE dune.setup_user_data_encryption(IN in_enable boolean)
 LANGUAGE plpgsql
AS $procedure$
declare
	encryption_key Text;
    encryption_key_hash bytea;
    stored_encryption_key_hash bytea;
	stored_encryption_status UserDataEncryptionStatus;
begin
	select current_setting('funcom.user_data_encryption_key', true) into encryption_key;
    -- might be null if the encryption_key is null
    select ext.digest(encryption_key, 'md5') into encryption_key_hash;
    -- might be null if the stored data is not encrypted
    select get_stored_user_data_encryption_key_hash() into stored_encryption_key_hash;

	-- should never be null
	select get_stored_user_data_encryption_status() into stored_encryption_status;

    drop index if exists encrypted_player_state_character_name_gin;

	if stored_encryption_status = 'Disabled' then
		if in_enable then
			perform _user_data_encryption_setup_enabled(encryption_key_hash);
			perform _user_data_encryption_initially_encrypt_existing_data();
		else
			-- technically we shouldn't do anything but let's just validate a few things
			-- we not replacing get_stored_user_data_encryption_key_hash()
			if (select get_stored_user_data_encryption_key_hash()) is not null then
				-- should have been filtered by the main setup function
				raise exception 'The data is encrypted, should use the taint version';
			end if;

			if (select get_stored_user_data_encryption_taint_xmax()) is not null then
				-- should have been filtered by the main setup function
				raise exception 'The data is tainted, should use the taint version';
			end if;
		end if;
	elseif stored_encryption_status = 'Tainted' then
		-- doesn't matter what we want, we get the tainted version
		perform _user_data_encryption_setup_tainted();
	else -- Enabled
		if in_enable is null or in_enable then
			if encryption_key_hash = stored_encryption_key_hash then -- and our key is the same
				perform _user_data_encryption_setup_enabled(encryption_key_hash);
			else
				raise warning 'User-data encryption requested but the data is already encrypted with a different key';
				perform _user_data_encryption_setup_tainted();
			end if;
		else
			raise warning 'User-data encryption not requested but the data is already encrypted';
			perform _user_data_encryption_setup_tainted();
		end if;
	end if;

	commit;

	CREATE INDEX encrypted_player_state_character_name_gin ON encrypted_player_state USING GIN((decrypt_user_data(encrypted_character_name)) ext.gin_trgm_ops);
end
$procedure$

@@@ENDRTN@@@
@@@RTN@@@58598
f
store_backup_vehicle
in_vehicle_id bigint, in_account_id bigint, in_customization_id text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.store_backup_vehicle(in_vehicle_id bigint, in_account_id bigint, in_customization_id text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    -- Lock the backup_vehicles table exclusively to prevent race conditions.
    LOCK TABLE backup_vehicles IN EXCLUSIVE MODE;
 
     -- Check if the vehicle already belongs to an actor state
    PERFORM 1 FROM actor_state WHERE actor_id = in_vehicle_id;
    IF FOUND THEN
        RAISE EXCEPTION 'Trying to backup vehicle % that already has an actor state.', in_vehicle_id;
    END IF;
 
    -- Check if the vehicle is already in backup_vehicles.
    PERFORM 1 FROM backup_vehicles WHERE vehicle_id = in_vehicle_id;
    IF FOUND THEN
        RAISE EXCEPTION 'Vehicle % is already stored as a backup.', in_vehicle_id;
    END IF;
 
    -- Check if the vehicle exists in the actors table.
    PERFORM 1 FROM actors WHERE id = in_vehicle_id;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Vehicle % does not exist in actors.', in_vehicle_id;
    END IF;
 
    -- Insert the vehicle into backup_vehicles.
    INSERT INTO backup_vehicles (account_id, vehicle_id, customization_id) 
	VALUES (in_account_id, in_vehicle_id, in_customization_id);
    RAISE INFO 'Inserted vehicle % into backup_vehicles for account %.', in_vehicle_id, in_account_id;
 
    -- Mark the actor as belonging to the vehicle backup tool
    INSERT INTO actor_state("actor_id", "state") VALUES(in_vehicle_id, 'VehicleBackup');
    RAISE INFO 'Inserted vehicle % into actor_state with VehicleBackup state', in_vehicle_id;
    
    PERFORM verify_item_dup_backup_tool(in_account_id, in_vehicle_id, 'item_dup_on_store_vbt');
 
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58599
f
store_recovered_vehicle
in_vehicle_id bigint, in_chassis_durability real, in_customization_id text, in_is_migration boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.store_recovered_vehicle(in_vehicle_id bigint, in_chassis_durability real, in_customization_id text, in_is_migration boolean DEFAULT false)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    owner_id BIGINT;
	player_id BIGINT;
	vehicle_class TEXT;
	vehicle_name TEXT;
BEGIN
    -- Lock the recovered_vehicles table exclusively to prevent race conditions.
    LOCK TABLE recovered_vehicles IN EXCLUSIVE MODE;
 
     -- Check if the vehicle already belongs to an actor state
    PERFORM 1 FROM actor_state WHERE actor_id = in_vehicle_id;
    IF FOUND THEN
        RAISE EXCEPTION 'Trying to store recovered vehicle % that already has an actor state.', in_vehicle_id;
    END IF;
 
    -- Check if the vehicle is already in recovered_vehicles.
    PERFORM 1 FROM recovered_vehicles WHERE vehicle_id = in_vehicle_id;
    IF FOUND THEN
        RAISE EXCEPTION 'Vehicle % is already recovered.', in_vehicle_id;
    END IF;
 
    -- Check if the vehicle exists in the actors table.
    SELECT class INTO vehicle_class
	FROM actors 
	WHERE id = in_vehicle_id
	LIMIT 1;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Vehicle % does not exist in actors.', in_vehicle_id;
    END IF;
 
	-- get the vehicles owner account id
	SELECT a.owner_account_id, r.player_id, p.actor_name INTO owner_id, player_id, vehicle_name
	FROM actors a
	INNER JOIN permission_actor_rank r ON r.player_id = a.id
	INNER JOIN permission_actor p ON p.actor_id = r.permission_actor_id
	WHERE r.permission_actor_id = in_vehicle_id
	AND r.rank = 1::smallint
	LIMIT 1;
	
	IF NOT FOUND THEN
		RAISE EXCEPTION 'No account_id found for vehicle: %', in_vehicle_id;
	END IF;
	
    -- Insert the vehicle into recovered_vehicles.
    INSERT INTO recovered_vehicles(account_id, vehicle_id, chassis_durability, vehicle_name, customization_id, migrated) 
	VALUES (owner_id, in_vehicle_id, in_chassis_durability, vehicle_name, in_customization_id, in_is_migration);
    RAISE INFO 'Inserted vehicle % into recovered_vehicles for account %.', in_vehicle_id, owner_id;
 
    -- Mark the actor as belonging to the vehicle recovery tool
    INSERT INTO actor_state("actor_id", "state") VALUES(in_vehicle_id, 'VehicleRecovery');
    RAISE INFO 'Inserted vehicle % into actor_state with VehicleRecovery state', in_vehicle_id;
    
    PERFORM pg_notify('vehicle_recovery_notify_channel', FORMAT('stored#{"PlayerId":%s, "VehicleId":%s, "VehicleClass":"%s", "VehicleName":"%s", "VehicleCustomizationId":"%s", "TimeStored":"%s", "ChassisDurability":%s, "bIsMigrated":%s}', player_id, in_vehicle_id, vehicle_class, vehicle_name, in_customization_id, NOW()  AT TIME ZONE 'UTC', in_chassis_durability, CASE WHEN in_is_migration THEN 'true' ELSE 'false' END) );
 
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58600
f
store_recovered_vehicles_wiped_before_spawn
in_vehicle_ids bigint[], in_delete_items boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.store_recovered_vehicles_wiped_before_spawn(in_vehicle_ids bigint[], in_delete_items boolean DEFAULT true)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	to_store RECORD;
BEGIN
	FOR to_store IN
		SELECT v.id AS vehicle_id, a.properties -> (regexp_replace("class", '^.*\.', '')) ->> 'm_CustomizationId' AS customization_id
		FROM actors a
		INNER JOIN vehicles v ON a.id = v.id
		INNER JOIN permission_actor p ON a.id = p.actor_id
		INNER JOIN permission_actor_rank r ON p.actor_id = r.permission_actor_id
		WHERE v.id = ANY(in_vehicle_ids)
		AND r.rank = 1::smallint
		AND NOT EXISTS
		(
			SELECT 1 FROM actor_state
			WHERE actor_state.actor_id = v.id
			AND actor_state.state IS DISTINCT FROM 'Default'
		)
	LOOP
		-- remove all items in all inventories of that vehicle
		if in_delete_items then
			PERFORM delete_items_from_actor(to_store.vehicle_id);
		end if;
	
		-- note: storing hardcoded chassis durability because its not available from pure database :(
		-- this will only be shown incorrectly in UI though, the spawned vehicle will get the correct value
		PERFORM store_recovered_vehicle(to_store.vehicle_id, 0.25, to_store.customization_id);

		-- remove permissions so the stored vehicles don't count towards the vehicle limit (the owner is preserved in the store function above)
		PERFORM permission_actor_destroy(to_store.vehicle_id);
	END LOOP;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58601
f
takeover_account
in_user_to_takeover text, in_current_user text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.takeover_account(in_user_to_takeover text, in_current_user text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	current_funcom_id ByteA;
	current_account_id BigInt;

	takeover_funcom_id ByteA;
	takeover_account_id BigInt;
BEGIN
	select "encrypted_funcom_id", "id" into current_funcom_id, current_account_id
		from encrypted_accounts WHERE "user"=in_current_user;

	select "encrypted_funcom_id", "id" into takeover_funcom_id, takeover_account_id
		from encrypted_accounts WHERE "user"=in_user_to_takeover;

	-- Account swap
	UPDATE encrypted_accounts SET "user"=in_current_user || 'TempTakeover' WHERE "id"=current_account_id;
	UPDATE encrypted_accounts SET "user"=in_current_user, "encrypted_funcom_id"=current_funcom_id WHERE "id"=takeover_account_id;
	UPDATE encrypted_accounts SET "user"=in_user_to_takeover, "encrypted_funcom_id"=takeover_funcom_id WHERE "id"=current_account_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58602
f
taxation_emit_invoices
new_tax_invoices dune.taxinvoicedata[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.taxation_emit_invoices(new_tax_invoices dune.taxinvoicedata[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	invoice_ids BIGINT[];
    new_invoice_id BIGINT;
    new_tax_invoice TaxInvoiceData;
BEGIN
    IF array_length(new_tax_invoices, 1) > 0 THEN
		FOREACH new_tax_invoice IN ARRAY new_tax_invoices LOOP
        
            INSERT INTO tax_invoice("totem_id", "reference_timespan", "invoice_status", "amount") 
                VALUES(new_tax_invoice.totem_id, new_tax_invoice.reference_timespan, new_tax_invoice.invoice_status, new_tax_invoice.amount) RETURNING id INTO new_invoice_id;
            
            invoice_ids := array_append(invoice_ids, new_invoice_id);
		END LOOP;

        PERFORM pg_notify('taxation_notify_channel', format('emit_invoices|{"InvoiceIds" : %s, "InvoiceData" : %s}', to_json(invoice_ids)::text, to_json(new_tax_invoices)::text));
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58603
f
taxation_get_all_invoices_for_player
in_player_id bigint
TABLE(id bigint, totem_id bigint, reference_timestamp bigint, invoice_status smallint, amount integer, actor_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.taxation_get_all_invoices_for_player(in_player_id bigint)
 RETURNS TABLE(id bigint, totem_id bigint, reference_timestamp bigint, invoice_status smallint, amount integer, actor_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT tax_invoice.id, tax_invoice.totem_id, tax_invoice.reference_timespan, tax_invoice.invoice_status, tax_invoice.amount, permission_actor.actor_name
	FROM tax_invoice 
	JOIN permission_actor ON permission_actor.actor_id = tax_invoice.totem_id
    JOIN permission_actor_rank ON permission_actor.actor_id = permission_actor_rank.permission_actor_id
    WHERE permission_actor_rank.player_id = in_player_id    
    ORDER BY tax_invoice.reference_timespan;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58604
f
taxation_get_all_invoices_for_server
map_name text, in_dimension_index integer, in_partition_id bigint
TABLE(id bigint, totem_id bigint, reference_timestamp bigint, invoice_status smallint, amount integer, actor_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.taxation_get_all_invoices_for_server(map_name text, in_dimension_index integer, in_partition_id bigint)
 RETURNS TABLE(id bigint, totem_id bigint, reference_timestamp bigint, invoice_status smallint, amount integer, actor_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT tax_invoice.id, actors.id, tax_invoice.reference_timespan, tax_invoice.invoice_status, tax_invoice.amount, permission_actor.actor_name
	FROM tax_invoice 
    JOIN permission_actor ON permission_actor.actor_id = tax_invoice.totem_id
	JOIN actors ON actors.id = tax_invoice.totem_id 
    WHERE actors.map = map_name
		AND actors.dimension_index = in_dimension_index
		AND ((in_partition_id = 0 AND actors.partition_id IS NULL) OR actors.partition_id = in_partition_id)
    ORDER BY tax_invoice.reference_timespan;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58605
f
taxation_get_all_invoices_for_totem
in_totem_id bigint
TABLE(id bigint, totem_id bigint, reference_timestamp bigint, invoice_status smallint, amount integer, actor_name text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.taxation_get_all_invoices_for_totem(in_totem_id bigint)
 RETURNS TABLE(id bigint, totem_id bigint, reference_timestamp bigint, invoice_status smallint, amount integer, actor_name text)
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN QUERY SELECT tax_invoice.id, actors.id, tax_invoice.reference_timespan, tax_invoice.invoice_status, tax_invoice.amount, permission_actor.actor_name
	FROM tax_invoice 
    JOIN permission_actor ON permission_actor.actor_id = tax_invoice.totem_id
	JOIN actors ON actors.id = tax_invoice.totem_id 
    WHERE actors.id = in_totem_id
    ORDER BY tax_invoice.reference_timespan;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58606
f
taxation_pay_invoice
invoice_id bigint, paid_invoice_status smallint
bigint

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.taxation_pay_invoice(invoice_id bigint, paid_invoice_status smallint)
 RETURNS bigint
 LANGUAGE plpgsql
AS $function$
DECLARE
	found_invoice_id BIGINT;
BEGIN
    UPDATE tax_invoice SET invoice_status = paid_invoice_status WHERE tax_invoice.id = invoice_id AND tax_invoice.invoice_status != paid_invoice_status RETURNING id INTO found_invoice_id;
    if found_invoice_id IS NOT NULL THEN
        PERFORM pg_notify('taxation_notify_channel', format('pay_invoice|{"InvoiceId" : %s }', invoice_id));
    END IF;
    RETURN found_invoice_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58607
f
taxation_remove_invoices
invoices_to_remove bigint[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.taxation_remove_invoices(invoices_to_remove bigint[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    DELETE FROM tax_invoice WHERE id = ANY(invoices_to_remove);
    PERFORM pg_notify('taxation_notify_channel', format('remove_invoice|{"InvoiceIds" : %s}', to_json(invoices_to_remove)::text));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58608
f
taxation_remove_invoices_from_totem
totem_actor_id bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.taxation_remove_invoices_from_totem(totem_actor_id bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	invoice_ids BIGINT[];
BEGIN
    SELECT array_agg(id) from tax_invoice into invoice_ids WHERE totem_id = totem_actor_id;

    DELETE FROM tax_invoice WHERE totem_id = totem_actor_id;
    PERFORM pg_notify('taxation_notify_channel', format('remove_invoice|{"InvoiceIds" : %s}', to_json(invoice_ids)::text));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58609
f
taxation_update_invoice_status
invoices_to_overdue bigint[], invoices_to_defaulted bigint[], overdue_invoice_status smallint, defaulted_invoice_status smallint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.taxation_update_invoice_status(invoices_to_overdue bigint[], invoices_to_defaulted bigint[], overdue_invoice_status smallint, defaulted_invoice_status smallint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    IF array_length(invoices_to_overdue, 1) > 0 THEN
        UPDATE tax_invoice SET invoice_status = overdue_invoice_status WHERE id = ANY(invoices_to_overdue);
        PERFORM pg_notify('taxation_notify_channel', format('update_invoice_status|{"InvoiceStatus" : %s, "InvoiceIds" : %s}', overdue_invoice_status, to_json(invoices_to_overdue)::text));
    END IF;

    IF array_length(invoices_to_defaulted, 1) > 0 THEN
        UPDATE tax_invoice SET invoice_status = defaulted_invoice_status WHERE id = ANY(invoices_to_defaulted);
        PERFORM pg_notify('taxation_notify_channel', format('update_invoice_status|{"InvoiceStatus" : %s, "InvoiceIds" : %s}', defaulted_invoice_status, to_json(invoices_to_defaulted)::text));
    END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58610
f
try_prime_spicefield
in_source_server_id text, in_spicefield_id integer
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.try_prime_spicefield(in_source_server_id text, in_spicefield_id integer)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
begin
	perform spicefield_type_id from spicefield_types as t
		where t.spicefield_type_id = in_spicefield_id and t.is_spawning_active is true and t.current_globally_primed < t.max_globally_primed and t.current_globally_active < t.max_globally_active;
	if not found then
		return false;
	end if;

	update spicefield_server_availability
	set inactive_fields_of_type = inactive_fields_of_type - 1
	where server_id = in_source_server_id and spicefield_type_id = in_spicefield_id;

	update spicefield_types
	set current_globally_primed = current_globally_primed + 1
	where spicefield_type_id = in_spicefield_id;

	return true;
	commit;
end $function$

@@@ENDRTN@@@
@@@RTN@@@58611
f
try_restart_spicefield
in_server_id text, in_spicefield_type_id integer
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.try_restart_spicefield(in_server_id text, in_spicefield_type_id integer)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
 BEGIN
	PERFORM spicefield_type_id
	FROM spicefield_types AS t
	WHERE t.spicefield_type_id = in_spicefield_type_id AND t.is_spawning_active IS TRUE;
	IF NOT FOUND THEN
		RETURN FALSE;
	END IF;

	UPDATE spicefield_server_availability
	SET inactive_fields_of_type = inactive_fields_of_type - 1
	WHERE server_id = in_server_id AND spicefield_type_id = in_spicefield_type_id;

	UPDATE spicefield_types
	SET current_globally_active = current_globally_active + 1
	WHERE spicefield_type_id = in_spicefield_type_id;

	RETURN TRUE;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58612
f
try_spawn_spicefield
in_source_server_id text, in_spicefield_id integer
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.try_spawn_spicefield(in_source_server_id text, in_spicefield_id integer)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
begin
	perform spicefield_type_id from spicefield_types as t
		where t.spicefield_type_id = in_spicefield_id and t.is_spawning_active is true and t.current_globally_active < t.max_globally_active;
	if not found then
		return false;
	end if;

	update spicefield_server_availability
	set requested_spawned_of_type = requested_spawned_of_type - 1
	where server_id = in_source_server_id and spicefield_type_id = in_spicefield_id;

	update spicefield_types
	set current_globally_active = current_globally_active + 1, current_globally_primed = current_globally_primed -1
	where spicefield_type_id = in_spicefield_id;

	return true;
	commit;
end $function$

@@@ENDRTN@@@
@@@RTN@@@58613
f
try_update_exchange_categories_hash
in_new_hash integer
TABLE(item_template_id text, mask integer, depth smallint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.try_update_exchange_categories_hash(in_new_hash integer)
 RETURNS TABLE(item_template_id text, mask integer, depth smallint)
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
	IF NOT EXISTS (SELECT * FROM dune_exchange_categories_hash) FOR UPDATE THEN
		INSERT INTO dune_exchange_categories_hash(id, hash) VALUES(1, in_new_hash);
		RETURN;
	END IF;
	IF NOT in_new_hash IN (SELECT hash FROM dune_exchange_categories_hash FOR SHARE) THEN
		UPDATE dune_exchange_categories_hash SET hash = in_new_hash WHERE dune_exchange_categories_hash.id = 1;
		RETURN QUERY SELECT DISTINCT template_id, category_mask, category_depth FROM dune_exchange_orders;
	END IF;
	RETURN;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58614
f
unassign_partition
in_server_id text
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.unassign_partition(in_server_id text)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
DECLARE
	v_affected_rows BigInt;
BEGIN
	UPDATE world_partition SET server_id = null WHERE server_id = in_server_id;
	get diagnostics v_affected_rows = ROW_COUNT;

	-- If we didn't actually unassign anything, we have no need to trigger the notification (as nothing changed)
	if v_affected_rows > 0 then
		NOTIFY world_partition_update;
		return true;
	end if;
	return false;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58615
f
update_communinet_player_channel
in_account_id bigint, in_channel_name text, in_is_tuned boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_communinet_player_channel(in_account_id bigint, in_channel_name text, in_is_tuned boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO communinet_player_channels(account_id, channel_name, is_tuned) VALUES (in_account_id, in_channel_name, in_is_tuned)
	ON CONFLICT(account_id, channel_name) DO UPDATE SET is_tuned = in_is_tuned WHERE communinet_player_channels.account_id = in_account_id AND communinet_player_channels.channel_name = in_channel_name;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58616
f
update_communinet_player_data
in_account_id bigint, in_is_active boolean, in_selected_channel_name text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_communinet_player_data(in_account_id bigint, in_is_active boolean, in_selected_channel_name text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO communinet_player(account_id, is_active, selected_channel_name) VALUES (in_account_id, in_is_active, in_selected_channel_name)
	ON CONFLICT(account_id) DO UPDATE SET is_active = in_is_active, selected_channel_name = in_selected_channel_name WHERE communinet_player.account_id = in_account_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58617
f
update_consumed_per_player_lore
in_actor_id bigint, in_consumed_bit_array bit, in_use_temporary boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_consumed_per_player_lore(in_actor_id bigint, in_consumed_bit_array bit, in_use_temporary boolean)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF in_use_temporary THEN
    	INSERT INTO consumed_temporary_per_player_lore(actor_id, consumed_bit_array)
    	VALUES(in_actor_id, in_consumed_bit_array)
    	ON CONFLICT (actor_id)
    	DO UPDATE SET
        	consumed_bit_array = in_consumed_bit_array;
	ELSE
    	INSERT INTO consumed_per_player_lore(actor_id, consumed_bit_array)
    	VALUES(in_actor_id, in_consumed_bit_array)
    	ON CONFLICT (actor_id)
    	DO UPDATE SET
        	consumed_bit_array = in_consumed_bit_array;
	END IF;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58618
f
update_coriolis_for_player
in_controller_id bigint, OUT out_was_coriolis_processed boolean
boolean

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_coriolis_for_player(in_controller_id bigint, OUT out_was_coriolis_processed boolean)
 RETURNS boolean
 LANGUAGE plpgsql
AS $function$
begin
	SELECT is_coriolis_processed INTO out_was_coriolis_processed
		FROM player_state WHERE player_controller_id = in_controller_id;
	UPDATE player_state SET is_coriolis_processed = TRUE WHERE player_controller_id = in_controller_id;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58619
f
update_death_location
in_pawn dune.actordescription, in_server_info dune.serverinfo, in_life_state dune.playerlifestate
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_death_location(in_pawn dune.actordescription, in_server_info dune.serverinfo, in_life_state dune.playerlifestate)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    IF in_life_state IS NOT NULL THEN
        UPDATE encrypted_player_state
        SET
            life_state = in_life_state,
            death_location = CASE
                WHEN in_life_state != 'Alive'::PlayerLifeState THEN ((in_pawn).transform.location, (in_server_info).map, (in_server_info).dimension_index)::DeathLocation
                ELSE null
            END
        WHERE player_pawn_id = in_pawn.id;
    END IF;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58620
f
update_farm_state
in_server_id text, in_outgoing_s2s_connections integer, in_incoming_s2s_connections integer, in_connected_players integer, in_farm_id text, in_igw_addr inet, in_igw_port integer, in_ready boolean, in_alive boolean, in_game_addr inet, in_game_port integer, in_map text, in_revision integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_farm_state(in_server_id text, in_outgoing_s2s_connections integer, in_incoming_s2s_connections integer, in_connected_players integer, in_farm_id text, in_igw_addr inet, in_igw_port integer, in_ready boolean, in_alive boolean, in_game_addr inet, in_game_port integer, in_map text, in_revision integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO farm_state
		(server_id, outgoing_s2s_connections, incoming_s2s_connections, connected_players, farm_id, igw_addr, igw_port, ready, alive, game_addr, game_port, map, revision)
	VALUES
		(in_server_id, in_outgoing_s2s_connections, in_incoming_s2s_connections, in_connected_players, in_farm_id, in_igw_addr, in_igw_port, in_ready, in_alive, in_game_addr, in_game_port, in_map, in_revision)
	ON CONFLICT(server_id) DO UPDATE SET
		outgoing_s2s_connections=in_outgoing_s2s_connections, incoming_s2s_connections=in_incoming_s2s_connections, connected_players=in_connected_players, farm_id=in_farm_id, igw_addr=in_igw_addr, igw_port=in_igw_port, ready=in_ready, alive=in_alive, game_addr=in_game_addr, game_port=in_game_port, map=in_map, revision=in_revision
	WHERE
		farm_state.server_id = in_server_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58621
f
update_global_spice_field_rules
in_max_globally_primed integer, in_max_globally_active integer, in_spicefield_type_id integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_global_spice_field_rules(in_max_globally_primed integer, in_max_globally_active integer, in_spicefield_type_id integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE spicefield_types SET max_globally_primed = in_max_globally_primed, max_globally_active = in_max_globally_active WHERE spicefield_type_id = in_spicefield_type_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58622
f
update_inventories_data
in_inventory_data_list dune.inventorydata[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_inventories_data(in_inventory_data_list dune.inventorydata[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	UPDATE inventories
    SET inventory_type = (u).inventory_type, max_item_count = (u).max_item_count, max_item_volume = (u).max_item_volume
        FROM (SELECT inventory_id, inventory_type, max_item_count, max_item_volume FROM UNNEST(in_inventory_data_list)) u
    WHERE id = (u).inventory_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58623
f
update_inventory
in_delete_list bigint[], in_stack_update dune.itemstackupdate[], in_quality_update dune.itemqualityupdate[], in_stat_update dune.itemstatupdate[], in_item_locations dune.inventoryitemlocation[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_inventory(in_delete_list bigint[], in_stack_update dune.itemstackupdate[], in_quality_update dune.itemqualityupdate[], in_stat_update dune.itemstatupdate[], in_item_locations dune.inventoryitemlocation[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    delete_item_id BIGINT;
BEGIN
    -- log item movement
    PERFORM _add_item_trace_log('update_inventory_locations', in_item_locations);
    
	-- delete items
	PERFORM delete_items(in_delete_list);

	-- update item stacks
	UPDATE items SET "stack_size" = (u).stack_size FROM (SELECT stack_size, item_id FROM UNNEST(in_stack_update)) u WHERE "id" = (u).item_id;

	-- update item quality
	UPDATE items SET "quality_level" = (u).quality_level FROM (SELECT quality_level, item_id FROM UNNEST(in_quality_update)) u WHERE "id" = (u).item_id;

	-- update stats
	UPDATE items SET "stats" = "stats" || (u).value FROM (SELECT value, item_id FROM UNNEST(in_stat_update)) u WHERE "id" = (u).item_id;

    UPDATE items
    SET inventory_id = (item).inventory_id, position_index = (item).position_index
        FROM (SELECT item_id, inventory_id, position_index FROM UNNEST(in_item_locations)) item
    WHERE id = (item).item_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58624
f
update_item_locations
in_item_locations dune.inventoryitemlocation[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_item_locations(in_item_locations dune.inventoryitemlocation[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE items
    SET inventory_id = (item).inventory_id, position_index = (item).position_index
        FROM (SELECT item_id, inventory_id, position_index FROM UNNEST(in_item_locations)) item
    WHERE id = (item).item_id;

	-- log item tracking
    PERFORM _add_item_trace_log('update_item_locations', in_item_locations);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58625
f
update_journey_story_ids
old_story_ids text[], new_story_ids text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_journey_story_ids(old_story_ids text[], new_story_ids text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
	index INT;
BEGIN
	IF array_length(old_story_ids, 1) != array_length(new_story_ids, 1) THEN
		RAISE EXCEPTION 'The length of the array of old IDs does not match the length of the array of new IDs - they must both have the same length';
	END IF;

	FOR index in 1 .. array_length(old_story_ids, 1) LOOP
		UPDATE journey_story_node
		SET story_node_id = new_story_ids[index]
		WHERE story_node_id = old_story_ids[index];

		UPDATE journey_story_node_cooldown
		SET story_node_id = new_story_ids[index]
		WHERE story_node_id = old_story_ids[index];
	END LOOP;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58626
f
update_marker_ids
in_old_ids integer[], in_new_ids integer[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_marker_ids(in_old_ids integer[], in_new_ids integer[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE markers SET marker_hash_id = data_table.new_id, marker.z = 0 from (select unnest(in_old_ids) as old_id, unnest(in_new_ids) as new_id) as data_table WHERE markers.marker_hash_id = data_table.old_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58627
f
update_partition_labels
in_allow_overwrite boolean
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_partition_labels(in_allow_overwrite boolean DEFAULT true)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	-- Compute candidate labels per-partition using the helper and apply them where appropriate
	UPDATE world_partition wp
	SET label = sub.new_label
	FROM (
		SELECT partition_id, determine_partition_label(map, dimension_index, label, in_allow_overwrite, partition_id) AS new_label
		FROM world_partition
	) AS sub
	WHERE wp.partition_id = sub.partition_id
	  AND (wp.label IS NULL OR in_allow_overwrite = true)
	  AND sub.new_label IS NOT NULL;

	-- The default is `MAP_DIMENSION`
	-- Only set it for map, dimension combos that have a single partition (label must be unique), and for labels not already touched
	UPDATE world_partition SET label = map || '_' || dimension_index
	from (
		select grouping.partition_ids[1] as partition_id
			from (
				select count(*) as count, array_agg(partition_id) as partition_ids
				from world_partition
				group by map, dimension_index
			) as grouping
			where grouping.count = 1
	) as partition
	where
		world_partition.partition_id = partition.partition_id
		and label is null;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58628
f
update_party_platform_session
in_party_id bigint, in_platform_session_id text, in_platform_name text
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_party_platform_session(in_party_id bigint, in_platform_session_id text, in_platform_name text)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF in_platform_session_id IS NULL OR length(in_platform_session_id) = 0 THEN
        RAISE EXCEPTION 'in_platform_session_id must not be empty';
    END IF;

	IF in_platform_name IS NULL OR length(in_platform_name) = 0 THEN
        RAISE EXCEPTION 'platform_name must not be empty';
    END IF;
	
	INSERT INTO platform_parties_mapping (platform_session_id, platform_name, dune_party_id)
	VALUES (in_platform_session_id, in_platform_name, in_party_id)
	ON CONFLICT (platform_name, dune_party_id)
	DO UPDATE SET platform_session_id = EXCLUDED.platform_session_id;

	PERFORM pg_notify('party_notify_channel', format('update_party_platform_id#{"PartyId" : %s, "PlatformSessionId" : "%s", "PlatformName" : "%s"}', 
		in_party_id, in_platform_session_id, in_platform_name));
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58629
f
update_player_tags
in_account_id bigint, tags_to_add text[], tags_to_remove text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_player_tags(in_account_id bigint, tags_to_add text[], tags_to_remove text[])
 RETURNS void
 LANGUAGE sql
AS $function$
    insert into player_tags("account_id", "tag") select in_account_id, unnest(tags_to_add) on conflict do nothing;
    delete from player_tags where account_id = in_account_id and tag = ANY(tags_to_remove);
$function$

@@@ENDRTN@@@
@@@RTN@@@58630
f
update_removed_items_and_recipes
items_removed text[], recipes_removed text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_removed_items_and_recipes(items_removed text[], recipes_removed text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
    insert into removed_items (name) select unnest(items_removed);
    insert into removed_recipes (name) select unnest(recipes_removed);
end;
$function$

@@@ENDRTN@@@
@@@RTN@@@58631
f
update_resourcefield_states
in_map text, in_dimension_index integer, in_field_kind_id smallint, in_field_states dune.resourcefieldstateentry[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_resourcefield_states(in_map text, in_dimension_index integer, in_field_kind_id smallint, in_field_states dune.resourcefieldstateentry[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	INSERT INTO resourcefield_state(map, dimension_index, field_kind_id, field_id, spawn_time, value_remaining) 
	SELECT in_map, in_dimension_index, in_field_kind_id, * FROM UNNEST(in_field_states)
	ON CONFLICT("field_id", "map", "dimension_index") DO
	UPDATE
	SET value_remaining = EXCLUDED.value_remaining 
	WHERE resourcefield_state.field_id = EXCLUDED.field_id AND resourcefield_state.map = in_map AND resourcefield_state.dimension_index = in_dimension_index;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58632
f
update_respawn_locations
player_id bigint, respawn_locations dune.respawnlocation[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_respawn_locations(player_id bigint, respawn_locations dune.respawnlocation[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    PERFORM 1
    FROM unnest(respawn_locations) AS loc
        WHERE
        (loc.locator).type != 'Invalid' AND NOT (
            (loc.locator).type = 'Transform' AND (loc.locator).transform IS NOT NULL AND (loc.locator).actor_id IS NULL AND (loc.locator).name IS NULL
            OR
            (loc.locator).type = 'PersistentActor' AND (loc.locator).actor_id IS NOT NULL AND (loc.locator).transform IS NULL AND (loc.locator).name IS NULL
            OR
            (loc.locator).type = 'StaticLocatorName' AND (loc.locator).name IS NOT NULL AND (loc.locator).transform IS NULL AND (loc.locator).actor_id IS NULL
        );

    IF FOUND THEN
        RAISE EXCEPTION 'Invalid respawn location in input array. Check locator type and associated fields.';
    END IF;

    WITH
    updated_respawn_locations AS (
        SELECT
            id,
            "group",
            (locator).transform AS locator_transform,
            (locator).actor_id AS locator_actor_id,
            (locator).name AS locator_name,
            (locator).name_index AS locator_name_index,
            map,
            dimension,
            last_used_timestamp
        FROM unnest(respawn_locations) AS updated
    ),
    delete_missing_respawn_locations AS (
        DELETE FROM player_respawn_locations AS existing
        WHERE NOT EXISTS (
            SELECT 1 FROM updated_respawn_locations AS updated WHERE updated.id = existing.id
        )
        AND existing.account_id = player_id
    )
    INSERT INTO player_respawn_locations(
        "id", "account_id", "group", "locator_transform", "locator_actor_id", "locator_name", "locator_name_index", "map", "dimension", "last_used_timestamp"
    )
    SELECT
        up.id, player_id, up.group, up.locator_transform, up.locator_actor_id, up.locator_name, up.locator_name_index, up.map, up.dimension, up.last_used_timestamp
    FROM updated_respawn_locations AS up
    WHERE up.locator_actor_id IS NULL OR EXISTS (
        SELECT 1 FROM actors WHERE id = up.locator_actor_id
    )
    ON CONFLICT ("id", "account_id")
    DO UPDATE SET
        "account_id" = EXCLUDED.account_id,
        "group" = EXCLUDED.group,
        "locator_transform" = EXCLUDED.locator_transform,
        "locator_actor_id" = EXCLUDED.locator_actor_id,
        "locator_name" = EXCLUDED.locator_name,
        "locator_name_index" = EXCLUDED.locator_name_index,
        "map" = EXCLUDED.map,
        "dimension" = EXCLUDED.dimension,
        "last_used_timestamp" = EXCLUDED.last_used_timestamp;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58633
f
update_returning_player_status
in_user_id text, in_minimum_returning_player_time_seconds integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_returning_player_status(in_user_id text, in_minimum_returning_player_time_seconds integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    user_account_id BigInt;
    last_login_time TIMESTAMPTZ;
    last_award_time TIMESTAMPTZ;
BEGIN
    SELECT INTO user_account_id, last_login_time, last_award_time id, ps.last_login_time, ps.last_returning_player_awarded_time
    FROM accounts acc
	JOIN player_state ps ON ps.account_id = acc.id
    WHERE acc.user=in_user_id;

    IF user_account_id IS NOT NULL THEN
        IF last_award_time + INTERVAL '1 second' * in_minimum_returning_player_time_seconds > CURRENT_TIMESTAMP THEN
            UPDATE player_state SET last_returning_player_event_time=NULL WHERE account_id=user_account_id;
        ELSIF last_login_time + INTERVAL '1 second' * in_minimum_returning_player_time_seconds < CURRENT_TIMESTAMP THEN
            UPDATE player_state SET last_returning_player_event_time=now() WHERE account_id=user_account_id;
        END IF;
    END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58634
f
update_sell_orders_categories
category_update_data dune.exchangecategoryupdatedata[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_sell_orders_categories(category_update_data dune.exchangecategoryupdatedata[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
	UPDATE dune_exchange_orders SET category_mask = update_data.mask, category_depth = update_data.depth
	FROM UNNEST(category_update_data) update_data
	WHERE update_data.item_template_id = dune_exchange_orders.template_id;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58635
f
update_server_building_favorites
in_account_id bigint, in_building_types text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_server_building_favorites(in_account_id bigint, in_building_types text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO building_favorites(account_id, building_types) 
    VALUES(in_account_id, in_building_types)
    ON CONFLICT(account_id) DO UPDATE SET building_types = in_building_types WHERE building_favorites.account_id = in_account_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58636
f
update_server_learned_building_sets
in_account_id bigint, in_learned_building_sets text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_server_learned_building_sets(in_account_id bigint, in_learned_building_sets text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO building_progression(account_id, learned_building_sets) 
    VALUES(in_account_id, in_learned_building_sets) 
    ON CONFLICT(account_id) DO UPDATE SET learned_building_sets = in_learned_building_sets WHERE building_progression.account_id = in_account_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58637
f
update_server_learned_new_buildable_pieces
in_account_id bigint, in_new_buildable_pieces text[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_server_learned_new_buildable_pieces(in_account_id bigint, in_new_buildable_pieces text[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    INSERT INTO building_progression(account_id, new_buildable_pieces) 
    VALUES(in_account_id, in_new_buildable_pieces) 
    ON CONFLICT(account_id) DO UPDATE SET new_buildable_pieces = in_new_buildable_pieces WHERE building_progression.account_id = in_account_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58638
f
update_specialization_refund_id
in_player_id bigint, in_refund_id smallint, in_removed_keystones smallint[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_specialization_refund_id(in_player_id bigint, in_refund_id smallint, in_removed_keystones smallint[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM purchased_specialization_keystones WHERE player_id = in_player_id AND keystone_id = ANY(in_removed_keystones);

	INSERT INTO specialization_refund_id (player_id, refund_id) VALUES(in_player_id, in_refund_id)
	ON conflict (player_id) DO UPDATE SET refund_id = in_refund_id;
END $function$

@@@ENDRTN@@@
@@@RTN@@@58639
f
update_spice_field_spawn_state
in_is_spawning_active boolean, in_spicefield_type_id integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_spice_field_spawn_state(in_is_spawning_active boolean, in_spicefield_type_id integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
    UPDATE spicefield_types SET is_spawning_active = in_is_spawning_active WHERE spicefield_type_id = in_spicefield_type_id;
    UPDATE spicefield_server_availability SET requested_spawned_of_type = 0 WHERE spicefield_type_id = in_spicefield_type_id;
END; $function$

@@@ENDRTN@@@
@@@RTN@@@58640
f
update_traveling_actor_dependencies
in_dep dune.traveldependency[]
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_traveling_actor_dependencies(in_dep dune.traveldependency[])
 RETURNS void
 LANGUAGE plpgsql
AS $function$
begin
	with valid_ids as (
		with d as (select * from unnest(in_dep))
		select d.id from d where d.id is not null
		union
		select d.parent_id from d where d.parent_id is not null
	),
	valid_ids_plus_dep as (
		select * from valid_ids
		union
		select tap.id from travel_actor_parent as tap where tap.parent_id in (select * from valid_ids)
	),
	-- remove valid ids and connected dependencies from actor_state
	delete_actor_state_ids AS (
		delete from actor_state as acs
		where acs.actor_id in (select * from valid_ids_plus_dep) and acs.state = 'Travel'
	)
	-- remove valid ids and connected dependencies from any other dependency tree
	delete from travel_actor_parent
	where id in (select * from valid_ids_plus_dep);
	
	-- add/update dependencies with valid parent and child ids
	with valid_dep as (
		select a1.id as id, a2.id as parent_id, d.is_instigator
		from unnest(in_dep) d
		left join actors as a1
			on d.id = a1.id
		left join actors as a2
			on d.parent_id = a2.id
		where
				a1.id is not null
			and a2.id is not null
	)
	insert into travel_actor_parent (id, parent_id, is_instigator)
		select * from valid_dep
	on conflict (id) do update set
		"parent_id" = excluded.parent_id,
		"is_instigator" = excluded.is_instigator;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58641
f
update_traveling_actor_tree
in_actor_id bigint, in_target_transform dune.transform, in_target_map text, in_target_dimension_index integer, in_target_partition_id bigint
TABLE(out_id bigint, out_actor_state text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_traveling_actor_tree(in_actor_id bigint, in_target_transform dune.transform, in_target_map text, in_target_dimension_index integer, in_target_partition_id bigint)
 RETURNS TABLE(out_id bigint, out_actor_state text)
 LANGUAGE plpgsql
AS $function$
begin
	RETURN query WITH
		traveling_actor_ids AS (
			SELECT t.id FROM get_traveling_actor_ids(in_actor_id) AS t
		),
		invalid_traveling_actor_ids AS (
			SELECT id, actor_state.state::TEXT FROM traveling_actor_ids
			INNER JOIN actor_state ON actor_state.actor_id = traveling_actor_ids.id
			WHERE actor_state.state != 'Travel'
		),
		valid_traveling_actor_ids AS (
			SELECT id FROM traveling_actor_ids
			WHERE NOT EXISTS (SELECT 1 FROM invalid_traveling_actor_ids)
		),
		insert_actor_state AS (
			INSERT INTO actor_state(actor_id, state)
			SELECT id, 'Travel' FROM valid_traveling_actor_ids
			ON CONFLICT DO NOTHING
		),
		update_actors AS (
			UPDATE actors
			SET 
				transform = in_target_transform,
				dimension_index = in_target_dimension_index,
				map = in_target_map,
				partition_id = in_target_partition_id
			FROM valid_traveling_actor_ids
			WHERE actors.id = valid_traveling_actor_ids.id
		)
	SELECT * FROM invalid_traveling_actor_ids;
end
$function$

@@@ENDRTN@@@
@@@RTN@@@58642
f
update_universe_time
in_farm_id text
TABLE(universe_time_timestamp timestamp without time zone, down_time_accumulation bigint)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_universe_time(in_farm_id text DEFAULT NULL::text)
 RETURNS TABLE(universe_time_timestamp timestamp without time zone, down_time_accumulation bigint)
 LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
	INSERT INTO farm_variables(farm_id, universe_time_timestamp, universe_lastactive_timestamp, down_time_accumulation, one_row)
	VALUES (in_farm_id, (CURRENT_TIMESTAMP AT TIME ZONE 'UTC')::TIMESTAMP, (CURRENT_TIMESTAMP AT TIME ZONE 'UTC')::TIMESTAMP, 0, true)
	ON CONFLICT(one_row) DO UPDATE
	SET
		down_time_accumulation = CASE
		WHEN farm_variables.farm_id != EXCLUDED.farm_id AND farm_variables.farm_id IS NOT NULL AND EXCLUDED.farm_id IS NOT NULL THEN farm_variables.down_time_accumulation + (EXTRACT(EPOCH FROM ((CURRENT_TIMESTAMP AT TIME ZONE 'UTC')::TIMESTAMP - farm_variables.universe_lastactive_timestamp)) * 1000000)::BIGINT
		ELSE farm_variables.down_time_accumulation
		END,
		universe_lastactive_timestamp = (CURRENT_TIMESTAMP AT TIME ZONE 'UTC')::TIMESTAMP,
		farm_id = CASE
		WHEN EXCLUDED.farm_id IS NULL AND farm_variables.farm_id IS NOT NULL THEN farm_variables.farm_id
		ELSE EXCLUDED.farm_id
		END;
	RETURN QUERY select * from get_universe_time();
	RETURN;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58643
f
update_vendor_timestamp_for_player
in_vendor_id text, in_player_id bigint, in_timestamp bigint
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.update_vendor_timestamp_for_player(in_vendor_id text, in_player_id bigint, in_timestamp bigint)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	IF NOT EXISTS
		( SELECT * FROM vendor_stock_cycle
		WHERE vendor_id = in_vendor_id AND player_id = in_player_id)
	THEN
		INSERT INTO vendor_stock_cycle(vendor_id, player_id, last_interacted_timestamp) VALUES(in_vendor_id, in_player_id, in_timestamp);
	ELSE
		UPDATE vendor_stock_cycle
		SET last_interacted_timestamp = in_timestamp
		WHERE vendor_id = in_vendor_id AND player_id = in_player_id;
	END IF;
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58644
f
upgrade_location_data_list
in_location_data_list jsonb, in_map_field_name text
jsonb

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.upgrade_location_data_list(in_location_data_list jsonb, in_map_field_name text)
 RETURNS jsonb
 LANGUAGE plpgsql
AS $function$
BEGIN
	return COALESCE(
		(
		SELECT jsonb_agg(
			CASE
				WHEN elem ? in_map_field_name
				THEN
					jsonb_set(
						elem,
						('{' || in_map_field_name ||'}')::Text[],
						upgrade_map_value(elem->in_map_field_name)
					)
				ELSE
					elem
			END
		)
		FROM jsonb_array_elements(in_location_data_list) elem
		),
		'[]'::jsonb
	);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58645
f
upgrade_map_name
in_map_name text
text

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.upgrade_map_name(in_map_name text)
 RETURNS text
 LANGUAGE plpgsql
AS $function$
BEGIN
	return
		CASE in_map_name
			WHEN 'Survival_1' THEN 'HaggaBasin'
			WHEN 'SH_HarkoVillage' THEN 'HarkoVillage'
			WHEN 'DeepDesert_1' THEN 'DeepDesert'
			WHEN 'Overmap' THEN 'Overland'
			WHEN 'SH_Arrakeen' THEN 'Arrakeen'
			WHEN 'CB_Story_Hephaestus' THEN 'WreckOfHephaestus'
			WHEN 'CB_Story_Ecolab_Carthag' THEN 'BeneathCarthag'
			WHEN 'CB_SurvivalChallenge_Station_15' THEN 'Station15'
			WHEN 'CB_Story_WaterFatManor' THEN 'WaterFat'
			WHEN 'SH_FallenLight' THEN 'FallenLight'
			WHEN 'Story_ProcesVerbal' THEN 'ProcesVerbal'
			WHEN 'DLC_Story_LostHarvest' THEN 'LostHarvest'
			WHEN 'DLC_Story_LostHarvest_EcolabA' THEN 'LostHarvest_EcolabA'
			WHEN 'DLC_Story_LostHarvest_EcolabB' THEN 'LostHarvest_EcolabB'
			WHEN 'DLC_Story_LostHarvest_ForgottenLab' THEN 'LostHarvest_ForgottenLab'
			WHEN 'Story_ArtOfKanly' THEN 'ArtOfKanly'
			WHEN 'Story_HeighlinerDungeon' THEN 'HeighlinerDungeon'
			WHEN 'CB_Dungeon_Hephaestus' THEN 'WreckOfHephaestusDungeon'
			WHEN 'CB_Dungeon_OldCarthag' THEN 'OldCarthagDungeon'
			WHEN 'CB_Story_BanditFortress01' THEN 'SandfliesFortress'
			WHEN 'CB_Overland_S_05' THEN 'ClosedOffTestingStationIsland'
			WHEN 'CB_Overland_S_06' THEN 'GroundVehicleTimeTrialIsland'
			WHEN 'CB_Overland_S_04' THEN 'ErythriteCaveIsland'
			WHEN 'CB_Overland_M_01' THEN 'RadioactiveShipwreck'
			WHEN 'CB_Overland_S_07' THEN 'TheRuinsOfTsimpo'
			WHEN 'Story_Faction_Outpost_Hark' THEN 'Story_Faction_Outpost_Hark'
			WHEN 'Story_Faction_Outpost_Atre' THEN 'Story_Faction_Outpost_Atre'
			WHEN 'CB_Ecolab_Bronze_Green_089' THEN 'RadiationDungeon'
			WHEN 'CB_Ecolab_Bronze_Green_152' THEN 'ElectricityDungeon'
			WHEN 'CB_Ecolab_Bronze_Green_195' THEN 'PoisonDungeon'
			WHEN 'CB_Ecolab_Bronze_Green_024' THEN 'DarknessDungeon'
			WHEN 'CB_Ecolab_Bronze_Green_136' THEN 'FireDungeon'
			WHEN 'CB_Story_DestroyedZanovar' THEN 'DestroyedZanovar'
			WHEN 'CB_Story_OrbitalMonitor' THEN 'OrbitalMonitor'
			WHEN 'CB_Dungeon_TheFacility' THEN 'FacilityDungeon'
			WHEN 'CB_Dungeon_ThePit' THEN 'PitDungeon'
			WHEN 'CB_Overland_S_08' THEN 'WindPass'
			ELSE in_map_name
		END;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58646
f
upgrade_map_value
in_value jsonb
jsonb

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.upgrade_map_value(in_value jsonb)
 RETURNS jsonb
 LANGUAGE plpgsql
AS $function$
BEGIN
	RETURN 
		CASE 
			WHEN jsonb_typeof(in_value) = 'string'
			THEN
				jsonb_build_object('Name', upgrade_map_name(in_value->>0))
			ELSE 
				in_value
		END;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58647
f
upsert_spicefield_types
in_max_globally_active integer[], in_max_globally_primed integer[], in_field_types text[], in_map_name text, in_dimension_index integer
TABLE(type_id integer, max_global integer, max_global_primed integer, spawning_active boolean, out_field_type text)

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.upsert_spicefield_types(in_max_globally_active integer[], in_max_globally_primed integer[], in_field_types text[], in_map_name text, in_dimension_index integer)
 RETURNS TABLE(type_id integer, max_global integer, max_global_primed integer, spawning_active boolean, out_field_type text)
 LANGUAGE plpgsql
AS $function$
begin
	insert into spicefield_types (max_globally_active, max_globally_primed, field_type, map_name, dimension_index)
		select unnest(in_max_globally_active), unnest(in_max_globally_primed), unnest(in_field_types), in_map_name, in_dimension_index
		on conflict do nothing;
	return query select spicefield_type_id, max_globally_active, max_globally_primed, is_spawning_active, field_type::text from spicefield_types where map_name = in_map_name and dimension_index = in_dimension_index;
end $function$

@@@ENDRTN@@@
@@@RTN@@@58648
f
use_sinkchart
in_player_id bigint, in_account_id bigint, in_area_id smallint, in_item_id bigint, in_sinkchart_map_name text, in_player_map_name text, in_player_current_dimension integer
dune.usesinkchartreturndata

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.use_sinkchart(in_player_id bigint, in_account_id bigint, in_area_id smallint, in_item_id bigint, in_sinkchart_map_name text, in_player_map_name text, in_player_current_dimension integer)
 RETURNS dune.usesinkchartreturndata
 LANGUAGE plpgsql
AS $function$
DECLARE
    sinkchart_markers SinkchartMarkerData[];
    survey_target JSONB;
BEGIN
    -- Update map areas.
    INSERT INTO map_areas (account_id, time_first_entered, time_discovered, area_id, items_surveyed_target, map_name)
    SELECT in_account_id, NULL, NOW(), in_area_id, map_areas.items_surveyed_target, in_sinkchart_map_name
    FROM map_areas
    WHERE map_areas.area_id = in_area_id
      AND map_areas.map_name = in_sinkchart_map_name
      AND map_areas.items_surveyed_target IS NOT NULL
    LIMIT 1
    ON CONFLICT ON CONSTRAINT map_areas_pkey DO UPDATE
        SET items_surveyed_target = EXCLUDED.items_surveyed_target,
            time_discovered = CASE WHEN map_areas.time_discovered IS NULL THEN EXCLUDED.time_discovered ELSE map_areas.time_discovered END
    RETURNING items_surveyed_target INTO survey_target;

    -- Perform the INSERT operation explicitly.
    INSERT INTO player_markers (player_id, marker_hash_id, dimension_index, map_name_id, discovery_level, discovery_method, payload)
    SELECT DISTINCT ON (m.marker_hash_id, m.dimension_index)
        in_player_id, m.marker_hash_id, m.dimension_index, m.map_name_id, /*discovery_level*/ 2, /*discovery_method*/ 12, /*payload*/ '{}'::JSONB
    FROM (
        SELECT UNNEST(sc.marker_hash_ids) AS marker_hash_id
        FROM sinkcharts sc
        WHERE sc.item_id = in_item_id
    ) expanded_ids
    JOIN map_names mn
        ON mn.map_name = in_sinkchart_map_name
    INNER JOIN markers m
        ON expanded_ids.marker_hash_id = m.marker_hash_id AND m.map_name_id = mn.map_name_id
    ORDER BY m.marker_hash_id, m.dimension_index
    ON CONFLICT ON CONSTRAINT player_markers_pkey DO UPDATE
        SET discovery_level = GREATEST(player_markers.discovery_level, EXCLUDED.discovery_level);

    -- Get markers that should be shown to the player immediately (marker on same map and dimension).
    SELECT ARRAY_AGG(ROW(m.marker_hash_id, m.marker, m.area_id, m.area_radius, m.long_range, pm.payload, m.payload)::SinkchartMarkerData)
    INTO sinkchart_markers
    FROM (
        SELECT UNNEST(sc.marker_hash_ids) AS marker_hash_id
        FROM sinkcharts sc
        WHERE sc.item_id = in_item_id
    ) expanded_ids
    JOIN map_names smn
        ON smn.map_name = in_sinkchart_map_name
    INNER JOIN markers m
        ON expanded_ids.marker_hash_id = m.marker_hash_id AND m.map_name_id = smn.map_name_id
    INNER JOIN player_markers pm
        ON m.marker_hash_id = pm.marker_hash_id
        AND m.dimension_index = pm.dimension_index
        AND m.map_name_id = pm.map_name_id
        AND pm.player_id = in_player_id
    JOIN map_names pmn
        ON pmn.map_name = in_player_map_name
    WHERE m.map_name_id = pmn.map_name_id
      AND (m.dimension_index = -1 OR m.dimension_index = in_player_current_dimension);

    RETURN (sinkchart_markers, survey_target);
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58649
f
verify_item_dup_backup_tool
in_account_id bigint, in_vehicle_id bigint, in_cheat_type dune.cheat_type_enum
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.verify_item_dup_backup_tool(in_account_id bigint, in_vehicle_id bigint, in_cheat_type dune.cheat_type_enum)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
DECLARE
    v_FLS_id TEXT;
BEGIN
	PERFORM 1 FROM inventories inv JOIN items it ON it.inventory_id = inv.id WHERE inv.actor_id = in_vehicle_id;
 	IF FOUND THEN

		-- Delete all items in this vehicle's inventories (excluding module inventories)
        PERFORM delete_items(
            (
                SELECT array_agg(i.id)
                FROM items i
                JOIN inventories inv ON inv.id = i.inventory_id
                WHERE inv.actor_id = in_vehicle_id
                AND inv.inventory_type = 0   -- 0 = vehicle backpack
            )
        );

        -- Debug logging
    	SELECT acc."user"
		    INTO v_FLS_id
		    FROM accounts acc
		    WHERE acc.id = in_account_id
		    LIMIT 1;
        RAISE WARNING 'Trying to vbt vehicle that has not an empty inventory. Behavior: % Vehicle: %, Account: %, FLS: %', in_cheat_type, in_vehicle_id, in_account_id, v_FLS_id;

        -- DB tracking
        PERFORM log_cheating(v_FLS_id, in_cheat_type);

    END IF;
END;
$function$

@@@ENDRTN@@@
@@@RTN@@@58650
f
wipe_old_events_log
in_days_limit integer
void

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.wipe_old_events_log(in_days_limit integer)
 RETURNS void
 LANGUAGE plpgsql
AS $function$
BEGIN
	DELETE FROM game_events WHERE universe_time < to_timestamp(in_days_limit);
END
$function$

@@@ENDRTN@@@
@@@RTN@@@58651
f
zero_transform

dune.transform

@@@BODY@@@
CREATE OR REPLACE FUNCTION dune.zero_transform()
 RETURNS dune.transform
 LANGUAGE plpgsql
 IMMUTABLE
AS $function$
BEGIN
	RETURN (
		ROW(
      		ROW(0.0, 0.0, 0.0)::Vector,
      		ROW(0.0, 0.0, 0.0, 1.0)::Quaternion
    	)::Transform
  	);
END;
$function$

@@@ENDRTN@@@
