df_ls_structure 0.3.0-rc.1

A language server for Dwarf Fortress RAW files
Documentation
use crate::CreatureToken;
use df_ls_core::{ReferenceTo, Referenceable};
use df_ls_diagnostics::{hash_map, DMExtraInfo, DiagnosticsInfo};
use df_ls_syntax_analysis::{LoopControl, Token, TokenDeserialize, TreeCursor};
use indexmap::{map::Entry, IndexMap};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug, TokenDeserialize, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
pub enum GraphicsToken {
    #[token_de(token = "TILE_PAGE")]
    TilePage(TilePageToken),
    #[token_de(token = "CREATURE_GRAPHICS")]
    CreatureGraphics(CreatureGraphicsToken),
}
impl Default for GraphicsToken {
    fn default() -> Self {
        Self::TilePage(TilePageToken::default())
    }
}

#[derive(
    Serialize, Deserialize, Clone, Debug, Default, TokenDeserialize, PartialEq, Eq, Referenceable,
)]
pub struct TilePageToken {
    /// Argument 1 of `[TILE_PAGE:...]`
    #[token_de(token = "TILE_PAGE", on_duplicate_to_parent, primary_token)]
    #[referenceable(self_reference)]
    pub reference: Option<ReferenceTo<Self>>,
    /// The relative path to the image.
    /// This file is should be relative to the current file and should include the extension.
    /// Allowed extensions are: `png`, `bmp`, ...
    // TODO Filepath, unix encoded (`/`), relative path from current file and with extension.
    #[token_de(token = "FILE")]
    pub file: Option<String>,
    /// The dimensions or size of a tile (1 character).
    /// For a 32x32 tileset this is `32:32`.
    ///
    /// Arguments: `[TILE_DIM:height:width]`
    #[token_de(token = "TILE_DIM")]
    pub tile_dimensions: Option<(u32, u32)>,
    /// The dimensions or size of the page.
    /// For a 32x32 tileset with 10 rows and 12 columns this is `10:12`.
    /// So in this case the actual image should be: 32*12 and 32*10 = `384x320px`
    ///
    /// Arguments: `[PAGE_DIM:width:height]` (NOTE: flipped compared to `TILE_DIM`)
    #[token_de(token = "PAGE_DIM")]
    pub page_dimensions: Option<(u32, u32)>,
}

type CreatureGraphicsTokenArg = (
    ReferenceTo<TilePageToken>,
    u32,
    u32,
    ColorTypeEnum,
    TextureTypeEnum,
);
type TextureGraphicsTokenArg = (
    ReferenceTo<TilePageToken>,
    u32,
    u32,
    ColorTypeEnum,
    Option<TextureTypeEnum>,
);

#[derive(Serialize, Deserialize, Clone, Debug, TokenDeserialize, PartialEq, Eq)]
#[token_de(enum_value)]
pub enum TextureTypeEnum {
    #[token_de(token = "DEFAULT")]
    Default,
    #[token_de(token = "ADVENTURER")]
    Adventurer,
    /// Deprecated: Please user`Default`
    // TODO: issue #83
    #[token_de(token = "GUARD")]
    Guard,
    /// Deprecated: Please use `Default`
    // TODO: issue #83
    #[token_de(token = "ROYALGUARD")]
    RoyalGuard,
    /// Deprecated: Please use `Default`
    // TODO: issue #83
    #[token_de(token = "ANIMATED")]
    Animated,
    /// Deprecated: Please use `Default`
    // TODO: issue #83
    #[token_de(token = "GHOST")]
    Ghost,
}
impl Default for TextureTypeEnum {
    fn default() -> Self {
        Self::Default
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, TokenDeserialize, PartialEq, Eq)]
#[token_de(enum_value)]
pub enum ColorTypeEnum {
    #[token_de(token = "ADD_COLOR")]
    AddColor,
    #[token_de(token = "AS_IS")]
    AsIs,
}
impl Default for ColorTypeEnum {
    fn default() -> Self {
        Self::AddColor
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
pub struct CreatureGraphicsToken {
    /// Argument 1 of `[CREATURE_GRAPHICS:...]`
    // #[token_de(token = "CREATURE_GRAPHICS", on_duplicate_to_parent, primary_token)]
    pub reference: Option<ReferenceTo<CreatureToken>>,

    // All tokens "DEFAULT", "ADVENTURER", "GUARD", "ROYALGUARD", "ANIMATED", "GHOST".
    pub main_texture_tokens: IndexMap<String, Vec<TextureGraphicsTokenArg>>,

    // All others, including professions
    pub other_graphics_tokens: IndexMap<String, Vec<CreatureGraphicsTokenArg>>,
}

// Implement custom because of the large amount of tokens allowed here.
impl TokenDeserialize for CreatureGraphicsToken {
    fn deserialize_general_token(
        cursor: &mut TreeCursor,
        source: &str,
        diagnostics: &mut DiagnosticsInfo,
        mut new_self: Box<Self>,
    ) -> (LoopControl, Box<Self>) {
        let node = cursor.node();
        let primary_token_name = "CREATURE_GRAPHICS";

        let token = match Token::deserialize_tokens(cursor, source, diagnostics) {
            Ok(token) => token,
            Err(_err) => {
                // When token could not be parsed correctly.
                // Token could not be parsed, so we can consume it.
                // Because this will always fail.
                Token::consume_token(cursor).unwrap();
                return (LoopControl::ErrBreak, new_self);
            }
        };
        // No `Token::consume_token(&mut cursor)?;` here because the token will be consumed when
        // all arguments can be stored inside of object
        let primary_token_filled = new_self.reference.is_some();
        let primary_token_ref: Option<String> = Some(primary_token_name.to_owned());
        // Token Name
        if token.check_token_name(source, diagnostics, true).is_err() {
            return (LoopControl::Break, new_self);
        }
        let token_name = match token.checked_get_token_name(source, diagnostics, true) {
            Ok(arg) => arg,
            Err(_) => {
                return (LoopControl::ErrBreak, new_self);
            }
        };
        log::debug!(
            "Matching {} in {}",
            token_name.value,
            "CreatureGraphicsToken"
        );
        match token_name.value.as_ref() as &str {
            "CREATURE_GRAPHICS" => {
                if new_self.reference.is_some() {
                    // on_duplicate_to_parent
                    return (LoopControl::Break, new_self);
                }

                let value = TokenDeserialize::deserialize_tokens(
                    cursor,
                    source,
                    diagnostics,
                );
                if let Ok(value) = value {
                    new_self.reference = Some(*value);
                }
            }
            // All other tokens
            // Default: Used when no graphic for a profession is found
            //
            // Arguments: `[DEFAULT:tile_page_ref:tile_page_x:tile_page_y:color_type:(optional)texture_type]`
            token_ref @ ("DEFAULT"
            | "ADVENTURER"
            | "GUARD"
            | "ROYALGUARD"
            | "ANIMATED"
            | "GHOST") => {
                // primary_token_check
                if !primary_token_filled {
                    if let Some(primary_token_ref) = primary_token_ref {
                        diagnostics.add_message(
                            DMExtraInfo {
                                range: node.get_range(),
                                message_template_data: hash_map! {
                                    "expected_tokens" => format!("`{}`", primary_token_ref),
                                },
                            },
                            "token_is_missing",
                        );
                    } else {
                        diagnostics.add_message(
                            DMExtraInfo {
                                range: node.get_range(),
                                message_template_data: hash_map! {
                                    "expected_tokens" => "<Unknown>".to_owned(),
                                },
                            },
                            "token_is_missing",
                        );
                    }
                    return (LoopControl::ErrBreak, new_self);
                }

                let value: Result<Box<TextureGraphicsTokenArg>, _> = TokenDeserialize::deserialize_tokens(
                    cursor,
                    source,
                    diagnostics,
                );
                if let Ok(value) = value {
                    // Add to hashmap
                    // Check if already in hashmap
                    match new_self.main_texture_tokens.entry(token_ref.to_owned()) {
                        Entry::Occupied(mut occupied_entry) => {
                            // Add value to list of values
                            let entry = occupied_entry.get_mut();
                            entry.push(*value);
                        },
                        Entry::Vacant(empty_entry) => {
                            empty_entry.insert(vec![*value]);
                        }
                    }
                }
                // Don't go to next token, we are still matching this token.
                return (LoopControl::Continue, new_self);
            }
            // All other tokens
            token_ref @ ("STANDARD"
            | "CHILD"
            | "BABY"
            | "DRUNK"
            | "ADMINISTRATOR"
            | "ALCHEMIST"
            | "ANIMAL_CARETAKER"
            | "ANIMAL_DISSECTOR"
            | "ANIMAL_TRAINER"
            | "ARCHITECT"
            | "ARMORER"
            | "BEEKEEPER"
            | "BLACKSMITH"
            | "BONE_CARVER"
            | "BONE_SETTER"
            | "BOWYER"
            | "BREWER"
            | "BUTCHER"
            | "CARPENTER"
            | "CHEESE_MAKER"
            | "CLERK"
            | "CLOTHIER"
            | "COOK"
            | "CRAFTSMAN"
            | "DIAGNOSER"
            | "DOCTOR"
            | "DYER"
            | "ENGINEER"
            | "ENGRAVER"
            | "FARMER"
            | "FISHERMAN"
            | "FISHERY_WORKER"
            | "FISH_CLEANER"
            | "FISH_DISSECTOR"
            | "FURNACE_OPERATOR"
            | "GELDER"
            | "GEM_CUTTER"
            | "GEM_SETTER"
            | "GLASSMAKER"
            | "GLAZER"
            | "GUILDREP"
            | "HERBALIST"
            | "HUNTER"
            | "JEWELER"
            | "LEATHERWORKER"
            | "LYE_MAKER"
            | "MASON"
            | "MECHANIC"
            | "MERCHANT"
            | "MERCHANTBARON"
            | "MERCHANT_NOBILITY"
            | "MERCHANTPRINCE"
            | "METALCRAFTER"
            | "METALSMITH"
            | "MILKER"
            | "MILLER"
            | "MINER"
            | "OUTPOSTLIAISON"
            | "PLANTER"
            | "POTASH_MAKER"
            | "POTTER"
            | "PRESSER"
            | "PUMP_OPERATOR"
            | "RANGER"
            | "SHEARER"
            | "SIEGE_ENGINEER"
            | "SIEGE_OPERATOR"
            | "SOAP_MAKER"
            | "SPINNER"
            | "STONECRAFTER"
            | "STONEWORKER"
            | "STRAND_EXTRACTOR"
            | "SURGEON"
            | "SUTURER"
            | "TANNER"
            | "TAX_COLLECTOR"
            | "THRESHER"
            | "TRADER"
            | "TRAPPER"
            | "WAX_WORKER"
            | "WEAPONSMITH"
            | "WEAVER"
            | "WOODCRAFTER"
            | "WOODCUTTER"
            | "WOODWORKER"
            | "WOOD_BURNER"
            //-------- Military ---------
            | "AXEMAN"
            | "BLOWGUNMAN"
            | "BOWMAN"
            | "CHAMPION"
            | "CROSSBOWMAN"
            | "HAMMERMAN"
            | "LASHER"
            | "MACEMAN"
            | "MASTER_AXEMAN"
            | "MASTER_BLOWGUNMAN"
            | "MASTER_BOWMAN"
            | "MASTER_CROSSBOWMAN"
            | "MASTER_HAMMERMAN"
            | "MASTER_LASHER"
            | "MASTER_MACEMAN"
            | "MASTER_PIKEMAN"
            | "MASTER_SPEARMAN"
            | "MASTER_SWORDSMAN"
            | "MASTER_THIEF"
            | "MASTER_WRESTLER"
            | "PIKEMAN"
            | "RECRUIT"
            | "SPEARMAN"
            | "SWORDSMAN"
            | "THIEF"
            | "TRAINED_HUNTER"
            | "TRAINED_WAR"
            | "WRESTLER"
            //-------- Positions ---------
            | "ACOLYTE"
            | "ADVISOR"
            | "BARON"
            | "BARONESS"
            | "BARONESS_CONSORT"
            | "BARON_CONSORT"
            | "BEAST_HUNTER"
            | "BOOKKEEPER"
            | "BROKER"
            | "CAPTAIN"
            | "CAPTAIN_OF_THE_GUARD"
            | "CHIEF_MEDICAL_DWARF"
            | "COUNT"
            | "COUNTESS"
            | "COUNTESS_CONSORT"
            | "COUNT_CONSORT"
            | "CRIMINAL"
            | "DIPLOMAT"
            | "DRUID"
            | "DUCHESS"
            | "DUCHESS_CONSORT"
            | "DUKE"
            | "DUKE_CONSORT"
            | "EXECUTIONER"
            | "EXPEDITION_LEADER"
            | "GENERAL"
            | "HAMMERER"
            | "HIGH_PRIEST"
            | "HOARDMASTER"
            | "KING"
            | "KING_CONSORT"
            | "LEADER"
            | "LIEUTENANT"
            | "MANAGER"
            | "MAYOR"
            | "MILITIA_CAPTAIN"
            | "MILITIA_COMMANDER"
            | "MONARCH"
            | "MONARCH_CONSORT"
            | "MONSTER_SLAYER"
            | "OUTPOST_LIAISON"
            | "PRIEST"
            | "PRISONER"
            | "QUEEN"
            | "QUEEN_CONSORT"
            | "RANGER_CAPTAIN"
            | "SCOUT"
            | "SHERIFF"
            | "SLAVE"
            | "SNATCHER"
            | "TREASURER"
            //-------- Added in DF 0.42 & 0.44 ---------
            | "ASTRONOMER"
            | "BARD"
            | "BOOKBINDER"
            | "CHEMIST"
            | "DANCER"
            | "GEOGRAPHER"
            | "HISTORIAN"
            | "MATHEMATICIAN"
            | "MESSENGER"
            | "MONK"
            | "NATURALIST"
            | "PAPERMAKER"
            | "PEDDLER"
            | "PERFORMER"
            | "PHILOSOPHER"
            | "PILGRIM"
            | "POET"
            | "PROPHET"
            | "SAGE"
            | "SCHOLAR"
            | "SCRIBE"
            | "TAVERN_KEEPER"
            //-------- Custom Officials ---------
            // http://www.bay12forums.com/smf/index.php?topic=175434.msg8082510#msg8082510
            // Chancellor
            | "CUSTOM_OFFICIAL_0"
            // Justiciar
            | "CUSTOM_OFFICIAL_1"
            // Treasurer
            | "CUSTOM_OFFICIAL_2"
            // Counselor
            | "CUSTOM_OFFICIAL_3"
            // Chamberlain
            | "CUSTOM_OFFICIAL_4"
            // Master of beasts
            | "CUSTOM_OFFICIAL_5"
            // Butler
            | "CUSTOM_OFFICIAL_6"
            // Doctor
            | "CUSTOM_OFFICIAL_7"
            // Executioner
            | "CUSTOM_OFFICIAL_8"
            // Chef
            | "CUSTOM_OFFICIAL_9"
            // Housekeeper
            | "CUSTOM_OFFICIAL_10"
            //-------- Custom Market Officials ---------
            // Sewer official
            | "CUSTOM_MARKET_OFFICIAL_0"
            // Grain official
            | "CUSTOM_MARKET_OFFICIAL_1"
            // Fire official
            | "CUSTOM_MARKET_OFFICIAL_2"
            // Judge
            | "CUSTOM_MARKET_OFFICIAL_3"
            // Building official
            | "CUSTOM_MARKET_OFFICIAL_4"
            // Road official
            | "CUSTOM_MARKET_OFFICIAL_5"
            //-------- Other ---------
            | "DUNGEONMASTER"
            | "FORCED_ADMINISTRATOR"
            // TODO: Can be used?
            | "FORMER_MEMBER"
            // TODO: Can be used?
            | "FORMER_MERCENARY"
            // TODO: Can be used?
            | "FORMER_PRISONER"
            // TODO: Can be used?
            | "FORMER_SLAVE"
            // TODO: Can be used?
            | "HANGOUT"
            // TODO: Can be used?
            | "HOME"
            // TODO: Can be used?
            | "MERCENARY"
            // TODO: Can be used?
            | "MEMBER"
            // TODO: Can be used?
            | "ENEMY"
            | "SEAT_OF_POWER"
            | "SHOPKEEPER"
            | "WANDERER") => {
                // primary_token_check
                if !primary_token_filled {
                    if let Some(primary_token_ref) = primary_token_ref {
                        diagnostics.add_message(
                            DMExtraInfo {
                                range: node.get_range(),
                                message_template_data: hash_map! {
                                    "expected_tokens" => format!("`{}`", primary_token_ref),
                                },
                            },
                            "token_is_missing",
                        );
                    } else {
                        diagnostics.add_message(
                            DMExtraInfo {
                                range: node.get_range(),
                                message_template_data: hash_map! {
                                    "expected_tokens" => "<Unknown>".to_owned(),
                                },
                            },
                            "token_is_missing",
                        );
                    }
                    return (LoopControl::ErrBreak, new_self);
                }

                let value: Result<Box<CreatureGraphicsTokenArg>, _> = TokenDeserialize::deserialize_tokens(
                    cursor,
                    source,
                    diagnostics,
                );
                if let Ok(value) = value {
                    // Add to hashmap
                    // Check if already in hashmap
                    match new_self.other_graphics_tokens.entry(token_ref.to_owned()) {
                        Entry::Occupied(mut occupied_entry) => {
                            // Add value to list of values
                            let entry = occupied_entry.get_mut();
                            entry.push(*value);
                        },
                        Entry::Vacant(empty_entry) => {
                            empty_entry.insert(vec![*value]);
                        }
                    }
                }
                // Don't go to next token, we are still matching this token.
                return (LoopControl::Continue, new_self);
            }
            _ => {
                // If nothing changed
                if *new_self == Self::default() {
                    return (LoopControl::ErrBreak, new_self);
                }
                // Go back up to parent
                return (LoopControl::Break, new_self);
            }
        }
        (LoopControl::DoNothing, new_self)
    }

    fn get_allowed_tokens() -> Option<Vec<String>> {
        Some(vec![
            "CREATURE_GRAPHICS".to_owned(),
            "DEFAULT".to_owned(),
            "ADVENTURER".to_owned(),
            "GUARD".to_owned(),
            "ROYALGUARD".to_owned(),
            "ANIMATED".to_owned(),
            "GHOST".to_owned(),
            "STANDARD".to_owned(),
            "CHILD".to_owned(),
            "BABY".to_owned(),
            "DRUNK".to_owned(),
            "ADMINISTRATOR".to_owned(),
            "ALCHEMIST".to_owned(),
            "ANIMAL_CARETAKER".to_owned(),
            "ANIMAL_DISSECTOR".to_owned(),
            "ANIMAL_TRAINER".to_owned(),
            "ARCHITECT".to_owned(),
            "ARMORER".to_owned(),
            "BEEKEEPER".to_owned(),
            "BLACKSMITH".to_owned(),
            "BONE_CARVER".to_owned(),
            "BONE_SETTER".to_owned(),
            "BOWYER".to_owned(),
            "BREWER".to_owned(),
            "BUTCHER".to_owned(),
            "CARPENTER".to_owned(),
            "CHEESE_MAKER".to_owned(),
            "CLERK".to_owned(),
            "CLOTHIER".to_owned(),
            "COOK".to_owned(),
            "CRAFTSMAN".to_owned(),
            "DIAGNOSER".to_owned(),
            "DOCTOR".to_owned(),
            "DYER".to_owned(),
            "ENGINEER".to_owned(),
            "ENGRAVER".to_owned(),
            "FARMER".to_owned(),
            "FISHERMAN".to_owned(),
            "FISHERY_WORKER".to_owned(),
            "FISH_CLEANER".to_owned(),
            "FISH_DISSECTOR".to_owned(),
            "FURNACE_OPERATOR".to_owned(),
            "GELDER".to_owned(),
            "GEM_CUTTER".to_owned(),
            "GEM_SETTER".to_owned(),
            "GLASSMAKER".to_owned(),
            "GLAZER".to_owned(),
            "GUILDREP".to_owned(),
            "HERBALIST".to_owned(),
            "HUNTER".to_owned(),
            "JEWELER".to_owned(),
            "LEATHERWORKER".to_owned(),
            "LYE_MAKER".to_owned(),
            "MASON".to_owned(),
            "MECHANIC".to_owned(),
            "MERCHANT".to_owned(),
            "MERCHANTBARON".to_owned(),
            "MERCHANT_NOBILITY".to_owned(),
            "MERCHANTPRINCE".to_owned(),
            "METALCRAFTER".to_owned(),
            "METALSMITH".to_owned(),
            "MILKER".to_owned(),
            "MILLER".to_owned(),
            "MINER".to_owned(),
            "OUTPOSTLIAISON".to_owned(),
            "PLANTER".to_owned(),
            "POTASH_MAKER".to_owned(),
            "POTTER".to_owned(),
            "PRESSER".to_owned(),
            "PUMP_OPERATOR".to_owned(),
            "RANGER".to_owned(),
            "SHEARER".to_owned(),
            "SIEGE_ENGINEER".to_owned(),
            "SIEGE_OPERATOR".to_owned(),
            "SOAP_MAKER".to_owned(),
            "SPINNER".to_owned(),
            "STONECRAFTER".to_owned(),
            "STONEWORKER".to_owned(),
            "STRAND_EXTRACTOR".to_owned(),
            "SURGEON".to_owned(),
            "SUTURER".to_owned(),
            "TANNER".to_owned(),
            "TAX_COLLECTOR".to_owned(),
            "THRESHER".to_owned(),
            "TRADER".to_owned(),
            "TRAPPER".to_owned(),
            "WAX_WORKER".to_owned(),
            "WEAPONSMITH".to_owned(),
            "WEAVER".to_owned(),
            "WOODCRAFTER".to_owned(),
            "WOODCUTTER".to_owned(),
            "WOODWORKER".to_owned(),
            "WOOD_BURNER".to_owned(),
            "AXEMAN".to_owned(),
            "BLOWGUNMAN".to_owned(),
            "BOWMAN".to_owned(),
            "CHAMPION".to_owned(),
            "CROSSBOWMAN".to_owned(),
            "HAMMERMAN".to_owned(),
            "LASHER".to_owned(),
            "MACEMAN".to_owned(),
            "MASTER_AXEMAN".to_owned(),
            "MASTER_BLOWGUNMAN".to_owned(),
            "MASTER_BOWMAN".to_owned(),
            "MASTER_CROSSBOWMAN".to_owned(),
            "MASTER_HAMMERMAN".to_owned(),
            "MASTER_LASHER".to_owned(),
            "MASTER_MACEMAN".to_owned(),
            "MASTER_PIKEMAN".to_owned(),
            "MASTER_SPEARMAN".to_owned(),
            "MASTER_SWORDSMAN".to_owned(),
            "MASTER_THIEF".to_owned(),
            "MASTER_WRESTLER".to_owned(),
            "PIKEMAN".to_owned(),
            "RECRUIT".to_owned(),
            "SPEARMAN".to_owned(),
            "SWORDSMAN".to_owned(),
            "THIEF".to_owned(),
            "TRAINED_HUNTER".to_owned(),
            "TRAINED_WAR".to_owned(),
            "WRESTLER".to_owned(),
            "ACOLYTE".to_owned(),
            "ADVISOR".to_owned(),
            "BARON".to_owned(),
            "BARONESS".to_owned(),
            "BARONESS_CONSORT".to_owned(),
            "BARON_CONSORT".to_owned(),
            "BEAST_HUNTER".to_owned(),
            "BOOKKEEPER".to_owned(),
            "BROKER".to_owned(),
            "CAPTAIN".to_owned(),
            "CAPTAIN_OF_THE_GUARD".to_owned(),
            "CHIEF_MEDICAL_DWARF".to_owned(),
            "COUNT".to_owned(),
            "COUNTESS".to_owned(),
            "COUNTESS_CONSORT".to_owned(),
            "COUNT_CONSORT".to_owned(),
            "CRIMINAL".to_owned(),
            "DIPLOMAT".to_owned(),
            "DRUID".to_owned(),
            "DUCHESS".to_owned(),
            "DUCHESS_CONSORT".to_owned(),
            "DUKE".to_owned(),
            "DUKE_CONSORT".to_owned(),
            "EXECUTIONER".to_owned(),
            "EXPEDITION_LEADER".to_owned(),
            "GENERAL".to_owned(),
            "HAMMERER".to_owned(),
            "HIGH_PRIEST".to_owned(),
            "HOARDMASTER".to_owned(),
            "KING".to_owned(),
            "KING_CONSORT".to_owned(),
            "LEADER".to_owned(),
            "LIEUTENANT".to_owned(),
            "MANAGER".to_owned(),
            "MAYOR".to_owned(),
            "MILITIA_CAPTAIN".to_owned(),
            "MILITIA_COMMANDER".to_owned(),
            "MONARCH".to_owned(),
            "MONARCH_CONSORT".to_owned(),
            "MONSTER_SLAYER".to_owned(),
            "OUTPOST_LIAISON".to_owned(),
            "PRIEST".to_owned(),
            "PRISONER".to_owned(),
            "QUEEN".to_owned(),
            "QUEEN_CONSORT".to_owned(),
            "RANGER_CAPTAIN".to_owned(),
            "SCOUT".to_owned(),
            "SHERIFF".to_owned(),
            "SLAVE".to_owned(),
            "SNATCHER".to_owned(),
            "TREASURER".to_owned(),
            "ASTRONOMER".to_owned(),
            "BARD".to_owned(),
            "BOOKBINDER".to_owned(),
            "CHEMIST".to_owned(),
            "DANCER".to_owned(),
            "GEOGRAPHER".to_owned(),
            "HISTORIAN".to_owned(),
            "MATHEMATICIAN".to_owned(),
            "MESSENGER".to_owned(),
            "MONK".to_owned(),
            "NATURALIST".to_owned(),
            "PAPERMAKER".to_owned(),
            "PEDDLER".to_owned(),
            "PERFORMER".to_owned(),
            "PHILOSOPHER".to_owned(),
            "PILGRIM".to_owned(),
            "POET".to_owned(),
            "PROPHET".to_owned(),
            "SAGE".to_owned(),
            "SCHOLAR".to_owned(),
            "SCRIBE".to_owned(),
            "TAVERN_KEEPER".to_owned(),
            "CUSTOM_OFFICIAL_0".to_owned(),
            "CUSTOM_OFFICIAL_1".to_owned(),
            "CUSTOM_OFFICIAL_2".to_owned(),
            "CUSTOM_OFFICIAL_3".to_owned(),
            "CUSTOM_OFFICIAL_4".to_owned(),
            "CUSTOM_OFFICIAL_5".to_owned(),
            "CUSTOM_OFFICIAL_6".to_owned(),
            "CUSTOM_OFFICIAL_7".to_owned(),
            "CUSTOM_OFFICIAL_8".to_owned(),
            "CUSTOM_OFFICIAL_9".to_owned(),
            "CUSTOM_OFFICIAL_10".to_owned(),
            "CUSTOM_MARKET_OFFICIAL_0".to_owned(),
            "CUSTOM_MARKET_OFFICIAL_1".to_owned(),
            "CUSTOM_MARKET_OFFICIAL_2".to_owned(),
            "CUSTOM_MARKET_OFFICIAL_3".to_owned(),
            "CUSTOM_MARKET_OFFICIAL_4".to_owned(),
            "CUSTOM_MARKET_OFFICIAL_5".to_owned(),
            "DUNGEONMASTER".to_owned(),
            "FORCED_ADMINISTRATOR".to_owned(),
            "FORMER_MEMBER".to_owned(),
            "FORMER_MERCENARY".to_owned(),
            "FORMER_PRISONER".to_owned(),
            "FORMER_SLAVE".to_owned(),
            "HANGOUT".to_owned(),
            "HOME".to_owned(),
            "MERCENARY".to_owned(),
            "MEMBER".to_owned(),
            "ENEMY".to_owned(),
            "SEAT_OF_POWER".to_owned(),
            "SHOPKEEPER".to_owned(),
            "WANDERER".to_owned(),
        ])
    }
}