#pragma once #include "CoreMinimal.h" #include "Engine/DataAsset.h" #include "World/WorldMapProjection.h" #include "WorldMapDefinition.generated.h" class UTexture2D; // Every property here is EditAnywhere rather than the EditDefaultsOnly a data asset usually carries, because // this asset is *written by a script*: Python's set_editor_property refuses a property marked edit-on-default // when the object is an instance, and a UDataAsset is an instance. EditDefaultsOnly guards against editing a // placed actor's copy of something; there is no such copy here. /** * One picture of the whole world, and what to call it. Every layer is a different render of the same cylinder, * so they share the definition's projection and need not share a resolution. */ USTRUCT(BlueprintType) struct SALTY_API FWorldMapLayer { GENERATED_BODY() /** How code and the console name this layer. Matches the id in RawContent/World/MapArt/layers.json. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World Map") FName Id; /** What a person sees on the button. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World Map") FText DisplayName; /** Soft, because a map that is never opened should not cost 11 MB of texture to be in the level. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World Map") TSoftObjectPtr Texture; /** Why this layer exists, for whoever opens the asset next. Never shown to a player. */ UPROPERTY(EditAnywhere, Category = "World Map", meta = (MultiLine = true)) FString Note; }; /** * The map of one level: where the ground is on the picture, and which pictures there are. * * Written by Scripts/Authoring/create_world_map.py out of RawContent/World/Region.json and MapArt/layers.json, * so the projection here and the geometry of the landscape come from the same numbers and cannot drift. It is * an asset rather than a config entry because it is content, and because the editor tab and the game both read * it without either of them knowing that RawContent exists. */ UCLASS(BlueprintType) class SALTY_API UWorldMapDefinition : public UPrimaryDataAsset { GENERATED_BODY() public: /** The primary asset type, registered for scanning in Config/DefaultGame.ini. */ static const FPrimaryAssetType AssetType; virtual FPrimaryAssetId GetPrimaryAssetId() const override { return FPrimaryAssetId(AssetType, GetFName()); } /** Where the ground is on the art. See FWorldMapProjection: for L_World this is the whole planet. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World Map") FWorldMapProjection Projection; /** * The level this is a map of. How UWorldMapSubsystem decides a definition belongs to the world it is in. * * A path rather than a TSoftObjectPtr because it is only ever compared, never loaded: resolving it * would pull a 98-landscape level in to answer "is this the world I am already standing in". */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World Map", meta = (AllowedClasses = "/Script/Engine.World")) FSoftObjectPath Level; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World Map") TArray Layers; /** Which layer the map opens on. Falls back to the first layer when it names one that is not here. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World Map") FName DefaultLayer; /** What built this, so a map that looks wrong can be traced to the run that made it. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World Map", meta = (MultiLine = true)) FString BuiltFrom; const FWorldMapLayer* FindLayer(FName Id) const; /** DefaultLayer if it exists, otherwise the first layer, otherwise nullptr. */ const FWorldMapLayer* ResolveDefaultLayer() const; /** The id after Id, wrapping; NAME_None when there are no layers. For a "next layer" key or button. */ FName NextLayerId(FName Id) const; UFUNCTION(BlueprintCallable, Category = "World Map") TArray GetLayerIds() const; /** True when this can actually be drawn: a projection with an extent and at least one layer. */ UFUNCTION(BlueprintPure, Category = "World Map") bool IsUsable() const { return Projection.IsValid() && Layers.Num() > 0; } };