Example #1
0
    public void ChangePlayerModel(PedHash hash)
    {
        var model = new Model(hash);

        // only in recent script hook

        /*
         * if (!Game.Player.ChangeModel(model))
         * {
         *  UI.Notify("could not request model");
         * }
         */
        if (!model.IsInCdImage || !model.IsPed || !model.Request(1000))
        {
            UI.Notify("could not request model");
        }
        else
        {
            Function.Call(Hash.SET_PLAYER_MODEL, Game.Player, model.Hash);
            Function.Call(Hash.SET_PED_DEFAULT_COMPONENT_VARIATION, Game.Player.Character.Handle);
            if (hash == PedHash.FreemodeMale01 || hash == PedHash.FreemodeFemale01)
            {
                // must call SET_PED_HEAD_BLEND_DATA otherwise head overlays don't work
                var slot_key   = new SlotKey(SlotType.Parent, 0);
                var slot_value = new SlotValue(0, 0, 4, 4);
                var ped        = Game.Player.Character;
                SetSlotValue(ped, slot_key, slot_value);
            }
            data.Clear();
        }
        model.MarkAsNoLongerNeeded();
    }
Example #2
0
    public static int GetNumIndex1(Ped ped, SlotKey key)
    {
        switch (key.typ)
        {
        case SlotType.CompVar:
            return(Math.Max(1, Function.Call <int>(
                                Hash.GET_NUMBER_OF_PED_DRAWABLE_VARIATIONS, ped.Handle, key.id)));

        case SlotType.Prop:
            // no prop = extra drawable variation in this script
            return(Math.Max(1, Function.Call <int>(
                                Hash.GET_NUMBER_OF_PED_PROP_DRAWABLE_VARIATIONS, ped.Handle, key.id) + 1));

        case SlotType.HeadOverlay:
            // no overlay = extra overlay in this script
            return(Math.Max(1, Function.Call <int>(
                                Hash._GET_NUM_HEAD_OVERLAY_VALUES, key.id) + 1));

        case SlotType.Eye:
            // TODO: hardcoded for now; how to get this from native?
            return(32);

        case SlotType.Parent:
            return(
                Function.Call <int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 0) +
                Function.Call <int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 1) +
                Function.Call <int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 2) +
                Function.Call <int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 3)
                );

        default:
            return(1);
        }
    }
Example #3
0
    public static int GetNumIndex3(Ped ped, SlotKey key, int index1, int index2)
    {
        if (key.typ == SlotType.CompVar && key.id == 2) // hair
        {
            return(Function.Call <int>(Hash._GET_NUM_HAIR_COLORS));
        }
        else if (key.typ == SlotType.HeadOverlay && index1 >= 1)
        {
            ColorType color_type = mp_head_overlay_color_type[key.id];
            switch (color_type)
            {
            case ColorType.Hair:
                return(Function.Call <int>(Hash._GET_NUM_HAIR_COLORS));

            case ColorType.Makeup:
                // _GET_NUM_MAKEUP_COLORS
                return(Function.Call <int>(Hash._0xD1F7CA1535D22818));
            }
        }
        else if (key.typ == SlotType.Parent)
        {
            return(9); // resemblance
        }
        return(1);
    }
Example #4
0
 public SlotValue GetSlotValue(SlotKey slot)
 {
     if (data.ContainsKey(slot))
     {
         return(data[slot]);
     }
     else
     {
         return(new SlotValue(0, 0, 0, 0));
     }
 }
Example #5
0
        /// <summary>
        /// Gets or creates a mesh pass slot for this pass inside its <see cref="RenderPipeline" />.
        /// </summary>
        /// <param name="renderPass">The render pass.</param>
        /// <param name="effectName">Name of the effect.</param>
        /// <returns>A mesh pass slot.</returns>
        public int GetModelSlot(RenderPass renderPass, string effectName)
        {
            int meshPassSlot;
            var key = new SlotKey(renderPass, effectName);

            if (!modelSlotMapping.TryGetValue(key, out meshPassSlot))
            {
                modelSlotMapping[key] = meshPassSlot = modelSlotMapping.Count;
            }
            return(meshPassSlot);
        }
Example #6
0
        public void AddLocationScope(ItemKey item, ItemScope scope, LocationScope locationScope)
        {
            SlotKey      key = new SlotKey(item, scope);
            ItemLocation loc = Location(key);

            loc.LocScope = locationScope;
            if (!Locations.ContainsKey(locationScope))
            {
                Locations[locationScope] = new List <SlotKey>();
            }
            Locations[locationScope].Add(key);
        }
Example #7
0
    public static int GetNumIndex2(Ped ped, SlotKey key, int index1)
    {
        switch (key.typ)
        {
        case SlotType.CompVar:
            // drawable_id = index1
            return(Function.Call <int>(
                       Hash.GET_NUMBER_OF_PED_TEXTURE_VARIATIONS, ped.Handle, key.id, index1));

        case SlotType.Prop:
            if (index1 == 0)
            {
                // no prop, so no textures
                return(1);
            }
            else
            {
                // drawable_id = index1 - 1
                var num = Function.Call <int>(
                    Hash.GET_NUMBER_OF_PED_PROP_TEXTURE_VARIATIONS, ped.Handle, key.id, index1 - 1);
                // sometimes returns -1 or 0 if there are no textures
                return((num <= 0) ? 1 : num);
            }

        case SlotType.HeadOverlay:
            if (index1 == 0)
            {
                // clear overlay
                return(1);
            }
            else
            {
                // opacity: 0 -> 1.0, 7 -> 0.125, in steps of 0.125
                return(7);
            }

        case SlotType.Parent:
            return(
                Function.Call <int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 0) +
                Function.Call <int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 1) +
                Function.Call <int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 2) +
                Function.Call <int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 3)
                );

        default:
            return(1);
        }
    }
Example #8
0
 public static int GetNumIndex4(Ped ped, SlotKey key, int index1, int index2, int index3)
 {
     // hair: highlight color
     if (key.typ == SlotType.CompVar && key.id == 2)
     {
         return(Function.Call <int>(Hash._GET_NUM_HAIR_COLORS));
     }
     else if (key.typ == SlotType.Parent)
     {
         return(9); // skin tone
     }
     else
     {
         return(1);
     }
 }
Example #9
0
    public void FreemodeUndressSlot(Ped ped, SlotKey slot_key)
    {
        var slot_value = new SlotValue(0, 0, 0, 0);

        if (slot_key.typ == SlotType.CompVar)
        {
            var hash = (PedHash)ped.Model.Hash;
            if (hash == PedHash.FreemodeMale01)
            {
                slot_value.index1 = mp_male_undress_drawable[slot_key.id];
            }
            else if (hash == PedHash.FreemodeFemale01)
            {
                slot_value.index1 = mp_female_undress_drawable[slot_key.id];
            }
        }
        SetSlotValue(ped, slot_key, slot_value);
    }
Example #10
0
    public void AddClearAppearanceToMenu(UIMenu menu)
    {
        Action Clear = () =>
        {
            var ped = Game.Player.Character;
            foreach (SlotType slot_type in Enum.GetValues(typeof(SlotType)))
            {
                for (int slot_id = 0; slot_id < PedData.GetNumId(slot_type); slot_id++)
                {
                    var slot_key = new SlotKey(slot_type, slot_id);
                    ped_data.ClearSlot(ped, slot_key);
                }
            }
        };
        var clearitem = new UIMenuItem("Clear Appearance");

        AddItem(menu, clearitem, OnSelect: Clear);
    }
Example #11
0
    // Undress but keep all head overlays. Returns dictionary that can be used to redress.
    public Dictionary <SlotKey, SlotValue> FreemodeUndress(Ped ped)
    {
        var old_data = new Dictionary <SlotKey, SlotValue>();

        old_data.Clear();
        SlotType[] typs = { SlotType.CompVar, SlotType.Prop };
        foreach (SlotType slot_type in typs)
        {
            for (int slot_id = 0; slot_id < PedData.GetNumId(slot_type); slot_id++)
            {
                if (slot_id == 2)
                {
                    continue;               // don't "undress" hair
                }
                var slot_key = new SlotKey(slot_type, slot_id);
                old_data[slot_key] = GetSlotValue(slot_key);
                FreemodeUndressSlot(ped, slot_key);
            }
        }
        return(old_data);
    }
Example #12
0
    public void LoadActor(UIMenuItem item, int slot)
    {
        var path = GetActorFilename(slot);

        UI.Notify(String.Format("Loading actor from {0}", path));
        var reader = new XmlTextReader(path);

        while (reader.Read())
        {
            if (reader.Name == "SetPlayerModel")
            {
                ped_data.ChangePlayerModel(_pedhash[reader.GetAttribute("name")]);
            }
            else if (reader.Name == "SetSlotValue")
            {
                var key = new SlotKey(reader);
                var val = new SlotValue(reader);
                ped_data.SetSlotValue(Game.Player.Character, key, val);
            }
        }
    }
Example #13
0
 public void FreemodeUndressSlot(Ped ped, SlotKey slot_key)
 {
     var slot_value = new SlotValue(0, 0, 0, 0);
     if (slot_key.typ == SlotType.CompVar)
     {
         var hash = (PedHash)ped.Model.Hash;
         if (hash == PedHash.FreemodeMale01)
             slot_value.index1 = mp_male_undress_drawable[slot_key.id];
         else if (hash == PedHash.FreemodeFemale01)
             slot_value.index1 = mp_female_undress_drawable[slot_key.id];
     }
     SetSlotValue(ped, slot_key, slot_value);
 }
Example #14
0
 public int GetNumIndex4(Ped ped, SlotKey key)
 {
     var slot_value = GetSlotValue(key);
     return GetNumIndex4(ped, key, slot_value.index1, slot_value.index2, slot_value.index3);
 }
Example #15
0
 public void ChangePlayerModel(PedHash hash)
 {
     var model = new Model(hash);
     // only in recent script hook
     /*
     if (!Game.Player.ChangeModel(model))
     {
         UI.Notify("could not request model");
     }
     */
     if (!model.IsInCdImage || !model.IsPed || !model.Request(1000))
     {
         UI.Notify("could not request model");
     }
     else
     {
         Function.Call(Hash.SET_PLAYER_MODEL, Game.Player, model.Hash);
         Function.Call(Hash.SET_PED_DEFAULT_COMPONENT_VARIATION, Game.Player.Character.Handle);
         if (hash == PedHash.FreemodeMale01 || hash == PedHash.FreemodeFemale01)
         {
             // must call SET_PED_HEAD_BLEND_DATA otherwise head overlays don't work
             var slot_key = new SlotKey(SlotType.Parent, 0);
             var slot_value = new SlotValue(0, 0, 4, 4);
             var ped = Game.Player.Character;
             SetSlotValue(ped, slot_key, slot_value);
         }
         data.Clear();
     }
     model.MarkAsNoLongerNeeded();
 }
Example #16
0
 // Undress but keep all head overlays. Returns dictionary that can be used to redress.
 public Dictionary<SlotKey, SlotValue> FreemodeUndress(Ped ped)
 {
     var old_data = new Dictionary<SlotKey, SlotValue>();
     old_data.Clear();
     SlotType[] typs = { SlotType.CompVar, SlotType.Prop };
     foreach (SlotType slot_type in typs)
         for (int slot_id = 0; slot_id < PedData.GetNumId(slot_type); slot_id++)
         {
             if (slot_id == 2) continue; // don't "undress" hair
             var slot_key = new SlotKey(slot_type, slot_id);
             old_data[slot_key] = GetSlotValue(slot_key);
             FreemodeUndressSlot(ped, slot_key);
         }
     return old_data;
 }
Example #17
0
 public static int GetNumIndex3(Ped ped, SlotKey key, int index1, int index2)
 {
     if (key.typ == SlotType.CompVar && key.id == 2) // hair
     {
         return Function.Call<int>(Hash._GET_NUM_HAIR_COLORS);
     }
     else if (key.typ == SlotType.HeadOverlay && index1 >= 1)
     {
         ColorType color_type = mp_head_overlay_color_type[key.id];
         switch (color_type)
         {
             case ColorType.Hair:
                 return Function.Call<int>(Hash._GET_NUM_HAIR_COLORS);
             case ColorType.Makeup:
                 // _GET_NUM_MAKEUP_COLORS
                 return Function.Call<int>(Hash._0xD1F7CA1535D22818);
         }
     }
     else if (key.typ == SlotType.Parent)
         return 9; // resemblance
     return 1;
 }
Example #18
0
 public static int GetNumIndex4(Ped ped, SlotKey key, int index1, int index2, int index3)
 {
     // hair: highlight color
     if (key.typ == SlotType.CompVar && key.id == 2)
         return Function.Call<int>(Hash._GET_NUM_HAIR_COLORS);
     else if (key.typ == SlotType.Parent)
         return 9; // skin tone
     else
         return 1;
 }
Example #19
0
 public static int GetNumIndex1(Ped ped, SlotKey key)
 {
     switch (key.typ)
     {
         case SlotType.CompVar:
             return Math.Max(1, Function.Call<int>(
                 Hash.GET_NUMBER_OF_PED_DRAWABLE_VARIATIONS, ped.Handle, key.id));
         case SlotType.Prop:
             // no prop = extra drawable variation in this script
             return Math.Max(1, Function.Call<int>(
                 Hash.GET_NUMBER_OF_PED_PROP_DRAWABLE_VARIATIONS, ped.Handle, key.id) + 1);
         case SlotType.HeadOverlay:
             // no overlay = extra overlay in this script
             return Math.Max(1, Function.Call<int>(
                 Hash._GET_NUM_HEAD_OVERLAY_VALUES, key.id) + 1);
         case SlotType.Eye:
             // TODO: hardcoded for now; how to get this from native?
             return 32;
         case SlotType.Parent:
             return (
                 Function.Call<int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 0) +
                 Function.Call<int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 1) +
                 Function.Call<int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 2) +
                 Function.Call<int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 3)
                 );
         default:
             return 1;
     }
 }
Example #20
0
    public void AddSlotToMenu(UIMenu menu, string text, SlotKey slot_key)
    {
        var submenu     = AddSubMenu(menu, text);
        var index_names = PedData.GetIndexNames(slot_key.typ);
        var listitem1   = new UIMenuListItem(index_names[0], UI_LIST, 0);
        var listitem2   = new UIMenuListItem(index_names[1], UI_LIST, 0);
        var listitem3   = new UIMenuListItem(index_names[2], UI_LIST, 0);
        var listitem4   = new UIMenuListItem(index_names[3], UI_LIST, 0);
        var randomitem  = new UIMenuItem("Random");
        var clearitem   = new UIMenuItem("Clear");

        submenu.AddItem(listitem1);
        submenu.AddItem(listitem2);
        submenu.AddItem(listitem3);
        submenu.AddItem(listitem4);
        submenu.AddItem(randomitem);
        submenu.AddItem(clearitem);
        // when the menu is selected, we only want submenu to be
        // enabled if its model and/or texture can be changed
        menu.ParentMenu.OnItemSelect += (sender, item, index) =>
        {
            if (item == menu.ParentItem)
            {
                var ped = Game.Player.Character;
                submenu.ParentItem.Enabled = (
                    (PedData.GetNumIndex1(ped, slot_key) >= 2) ||
                    (PedData.GetNumIndex2(ped, slot_key, 0) >= 2) ||
                    (PedData.GetNumIndex3(ped, slot_key, 0, 0) >= 2) ||
                    (PedData.GetNumIndex4(ped, slot_key, 0, 0, 0) >= 2));
            }
        };
        // when submenu is selected, display correct indices for model and texture
        // and enable those entries of this submenu
        // (model, texture, ...) where something can be changed
        submenu.ParentMenu.OnItemSelect += (sender, item, index) =>
        {
            if (item == submenu.ParentItem)
            {
                var ped        = Game.Player.Character;
                var slot_value = ped_data.GetSlotValue(slot_key);
                listitem1.Index   = slot_value.index1;
                listitem1.Enabled = (PedData.GetNumIndex1(ped, slot_key) >= 2);
                listitem2.Index   = slot_value.index2;
                listitem2.Enabled = (ped_data.GetNumIndex2(ped, slot_key) >= 2);
                listitem3.Index   = slot_value.index3;
                listitem3.Enabled = (ped_data.GetNumIndex3(ped, slot_key) >= 2);
                listitem4.Index   = slot_value.index4;
                listitem4.Enabled = (ped_data.GetNumIndex4(ped, slot_key) >= 2);
            }
        };
        submenu.OnListChange += (sender, item, index) =>
        {
            if (item == listitem1 || item == listitem2 || item == listitem3 || item == listitem4)
            {
                var ped        = Game.Player.Character;
                var slot_value = ped_data.GetSlotValue(slot_key);
                var numIndex1  = PedData.GetNumIndex1(ped, slot_key);
                var numIndex2  = ped_data.GetNumIndex2(ped, slot_key);
                var numIndex3  = ped_data.GetNumIndex3(ped, slot_key);
                var numIndex4  = ped_data.GetNumIndex4(ped, slot_key);
                // we need to ensure that the new id is valid as the menu has more items than number of ids supported by the game
                int maxid;
                if (item == listitem1)
                {
                    maxid = numIndex1;
                }
                else if (item == listitem2)
                {
                    maxid = numIndex2;
                }
                else if (item == listitem3)
                {
                    maxid = numIndex3;
                }
                else // if (item == listitem4)
                {
                    maxid = numIndex4;
                }
                maxid = Math.Min(maxid - 1, UI_LIST_MAX);
                System.Diagnostics.Debug.Assert(maxid >= 0);
                System.Diagnostics.Debug.Assert(maxid <= UI_LIST_MAX - 1);
                if (index > maxid)
                {
                    // wrap the index depending on whether user scrolled forward or backward
                    index      = (index == UI_LIST_MAX - 1) ? maxid : 0;
                    item.Index = index;
                }
                if (item == listitem1)
                {
                    slot_value.index1 = index;
                }
                else if (item == listitem2)
                {
                    slot_value.index2 = index;
                }
                else if (item == listitem3)
                {
                    slot_value.index3 = index;
                }
                else // if (item == listitem4)
                {
                    slot_value.index4 = index;
                }
                // correct listitem2 if index2 is out of range
                var newNumIndex2 = PedData.GetNumIndex2(ped, slot_key, slot_value.index1);
                if (slot_value.index2 >= newNumIndex2)
                {
                    slot_value.index2 = newNumIndex2 - 1;
                }
                listitem2.Index   = slot_value.index2;
                listitem2.Enabled = (newNumIndex2 >= 2);
                // correct listitem3 if index3 is out of range
                var newNumIndex3 = PedData.GetNumIndex3(ped, slot_key, slot_value.index1, slot_value.index2);
                if (slot_value.index3 >= newNumIndex3)
                {
                    slot_value.index3 = newNumIndex3 - 1;
                }
                listitem3.Index   = slot_value.index3;
                listitem3.Enabled = (newNumIndex3 >= 2);
                // correct listitem3 if index3 is out of range
                var newNumIndex4 = PedData.GetNumIndex4(ped, slot_key, slot_value.index1, slot_value.index2, slot_value.index3);
                if (slot_value.index4 >= newNumIndex4)
                {
                    slot_value.index4 = newNumIndex4 - 1;
                }
                listitem4.Index   = slot_value.index4;
                listitem4.Enabled = (newNumIndex4 >= 2);
                // set slot value
                ped_data.SetSlotValue(ped, slot_key, slot_value);
            }
        };
        submenu.OnItemSelect += (sender, item, index) =>
        {
            if (item == clearitem)
            {
                var ped = Game.Player.Character;
                ped_data.ClearSlot(ped, slot_key);
                // update menu items
                var slot_value = ped_data.GetSlotValue(slot_key);
                listitem1.Index   = slot_value.index1;
                listitem2.Index   = slot_value.index2;
                listitem3.Index   = slot_value.index3;
                listitem4.Index   = slot_value.index4;
                listitem1.Enabled = (PedData.GetNumIndex1(ped, slot_key) >= 2);
                listitem2.Enabled = (ped_data.GetNumIndex2(ped, slot_key) >= 2);
                listitem3.Enabled = (ped_data.GetNumIndex3(ped, slot_key) >= 2);
                listitem4.Enabled = (ped_data.GetNumIndex4(ped, slot_key) >= 2);
            }
            else if (item == randomitem)
            {
                var ped        = Game.Player.Character;
                var slot_value = ped_data.GetSlotValue(slot_key);
                slot_value.index1 = rnd.Next(PedData.GetNumIndex1(ped, slot_key));
                slot_value.index2 = rnd.Next(PedData.GetNumIndex2(ped, slot_key, slot_value.index1));
                slot_value.index3 = rnd.Next(PedData.GetNumIndex3(ped, slot_key, slot_value.index1, slot_value.index2));
                slot_value.index4 = rnd.Next(PedData.GetNumIndex4(ped, slot_key, slot_value.index1, slot_value.index2, slot_value.index3));
                ped_data.SetSlotValue(ped, slot_key, slot_value);
                listitem1.Index   = slot_value.index1;
                listitem2.Index   = slot_value.index2;
                listitem3.Index   = slot_value.index3;
                listitem4.Index   = slot_value.index4;
                listitem1.Enabled = (PedData.GetNumIndex1(ped, slot_key) >= 2);
                listitem2.Enabled = (ped_data.GetNumIndex2(ped, slot_key) >= 2);
                listitem3.Enabled = (ped_data.GetNumIndex3(ped, slot_key) >= 2);
                listitem4.Enabled = (ped_data.GetNumIndex4(ped, slot_key) >= 2);
            }
        };
    }
Example #21
0
 public void AddSlotToMenu(UIMenu menu, string text, SlotKey slot_key)
 {
     var submenu = AddSubMenu(menu, text);
     var index_names = PedData.GetIndexNames(slot_key.typ);
     var listitem1 = new UIMenuListItem(index_names[0], UI_LIST, 0);
     var listitem2 = new UIMenuListItem(index_names[1], UI_LIST, 0);
     var listitem3 = new UIMenuListItem(index_names[2], UI_LIST, 0);
     var listitem4 = new UIMenuListItem(index_names[3], UI_LIST, 0);
     var randomitem = new UIMenuItem("Random");
     var clearitem = new UIMenuItem("Clear");
     submenu.AddItem(listitem1);
     submenu.AddItem(listitem2);
     submenu.AddItem(listitem3);
     submenu.AddItem(listitem4);
     submenu.AddItem(randomitem);
     submenu.AddItem(clearitem);
     // when the menu is selected, we only want submenu to be
     // enabled if its model and/or texture can be changed
     menu.ParentMenu.OnItemSelect += (sender, item, index) =>
     {
         if (item == menu.ParentItem)
         {
             var ped = Game.Player.Character;
             submenu.ParentItem.Enabled = (
                 (PedData.GetNumIndex1(ped, slot_key) >= 2) ||
                 (PedData.GetNumIndex2(ped, slot_key, 0) >= 2) ||
                 (PedData.GetNumIndex3(ped, slot_key, 0, 0) >= 2) ||
                 (PedData.GetNumIndex4(ped, slot_key, 0, 0, 0) >= 2));
         }
     };
     // when submenu is selected, display correct indices for model and texture
     // and enable those entries of this submenu
     // (model, texture, ...) where something can be changed
     submenu.ParentMenu.OnItemSelect += (sender, item, index) =>
     {
         if (item == submenu.ParentItem)
         {
             var ped = Game.Player.Character;
             var slot_value = ped_data.GetSlotValue(slot_key);
             listitem1.Index = slot_value.index1;
             listitem1.Enabled = (PedData.GetNumIndex1(ped, slot_key) >= 2);
             listitem2.Index = slot_value.index2;
             listitem2.Enabled = (ped_data.GetNumIndex2(ped, slot_key) >= 2);
             listitem3.Index = slot_value.index3;
             listitem3.Enabled = (ped_data.GetNumIndex3(ped, slot_key) >= 2);
             listitem4.Index = slot_value.index4;
             listitem4.Enabled = (ped_data.GetNumIndex4(ped, slot_key) >= 2);
         }
     };
     submenu.OnListChange += (sender, item, index) =>
     {
         if (item == listitem1 || item == listitem2 || item == listitem3 || item == listitem4)
         {
             var ped = Game.Player.Character;
             var slot_value = ped_data.GetSlotValue(slot_key);
             var numIndex1 = PedData.GetNumIndex1(ped, slot_key);
             var numIndex2 = ped_data.GetNumIndex2(ped, slot_key);
             var numIndex3 = ped_data.GetNumIndex3(ped, slot_key);
             var numIndex4 = ped_data.GetNumIndex4(ped, slot_key);
             // we need to ensure that the new id is valid as the menu has more items than number of ids supported by the game
             int maxid;
             if (item == listitem1)
                 maxid = numIndex1;
             else if (item == listitem2)
                 maxid = numIndex2;
             else if (item == listitem3)
                 maxid = numIndex3;
             else // if (item == listitem4)
                 maxid = numIndex4;
             maxid = Math.Min(maxid - 1, UI_LIST_MAX);
             System.Diagnostics.Debug.Assert(maxid >= 0);
             System.Diagnostics.Debug.Assert(maxid <= UI_LIST_MAX - 1);
             if (index > maxid)
             {
                 // wrap the index depending on whether user scrolled forward or backward
                 index = (index == UI_LIST_MAX - 1) ? maxid : 0;
                 item.Index = index;
             }
             if (item == listitem1)
                 slot_value.index1 = index;
             else if (item == listitem2)
                 slot_value.index2 = index;
             else if (item == listitem3)
                 slot_value.index3 = index;
             else // if (item == listitem4)
                 slot_value.index4 = index;
             // correct listitem2 if index2 is out of range
             var newNumIndex2 = PedData.GetNumIndex2(ped, slot_key, slot_value.index1);
             if (slot_value.index2 >= newNumIndex2) slot_value.index2 = newNumIndex2 - 1;
             listitem2.Index = slot_value.index2;
             listitem2.Enabled = (newNumIndex2 >= 2);
             // correct listitem3 if index3 is out of range
             var newNumIndex3 = PedData.GetNumIndex3(ped, slot_key, slot_value.index1, slot_value.index2);
             if (slot_value.index3 >= newNumIndex3) slot_value.index3 = newNumIndex3 - 1;
             listitem3.Index = slot_value.index3;
             listitem3.Enabled = (newNumIndex3 >= 2);
             // correct listitem3 if index3 is out of range
             var newNumIndex4 = PedData.GetNumIndex4(ped, slot_key, slot_value.index1, slot_value.index2, slot_value.index3);
             if (slot_value.index4 >= newNumIndex4) slot_value.index4 = newNumIndex4 - 1;
             listitem4.Index = slot_value.index4;
             listitem4.Enabled = (newNumIndex4 >= 2);
             // set slot value
             ped_data.SetSlotValue(ped, slot_key, slot_value);
         }
     };
     submenu.OnItemSelect += (sender, item, index) =>
     {
         if (item == clearitem)
         {
             var ped = Game.Player.Character;
             ped_data.ClearSlot(ped, slot_key);
             // update menu items
             var slot_value = ped_data.GetSlotValue(slot_key);
             listitem1.Index = slot_value.index1;
             listitem2.Index = slot_value.index2;
             listitem3.Index = slot_value.index3;
             listitem4.Index = slot_value.index4;
             listitem1.Enabled = (PedData.GetNumIndex1(ped, slot_key) >= 2);
             listitem2.Enabled = (ped_data.GetNumIndex2(ped, slot_key) >= 2);
             listitem3.Enabled = (ped_data.GetNumIndex3(ped, slot_key) >= 2);
             listitem4.Enabled = (ped_data.GetNumIndex4(ped, slot_key) >= 2);
         }
         else if (item == randomitem)
         {
             var ped = Game.Player.Character;
             var slot_value = ped_data.GetSlotValue(slot_key);
             slot_value.index1 = rnd.Next(PedData.GetNumIndex1(ped, slot_key));
             slot_value.index2 = rnd.Next(PedData.GetNumIndex2(ped, slot_key, slot_value.index1));
             slot_value.index3 = rnd.Next(PedData.GetNumIndex3(ped, slot_key, slot_value.index1, slot_value.index2));
             slot_value.index4 = rnd.Next(PedData.GetNumIndex4(ped, slot_key, slot_value.index1, slot_value.index2, slot_value.index3));
             ped_data.SetSlotValue(ped, slot_key, slot_value);
             listitem1.Index = slot_value.index1;
             listitem2.Index = slot_value.index2;
             listitem3.Index = slot_value.index3;
             listitem4.Index = slot_value.index4;
             listitem1.Enabled = (PedData.GetNumIndex1(ped, slot_key) >= 2);
             listitem2.Enabled = (ped_data.GetNumIndex2(ped, slot_key) >= 2);
             listitem3.Enabled = (ped_data.GetNumIndex3(ped, slot_key) >= 2);
             listitem4.Enabled = (ped_data.GetNumIndex4(ped, slot_key) >= 2);
         }
     };
 }
Example #22
0
 public void LoadActor(UIMenuItem item, int slot)
 {
     var path = GetActorFilename(slot);
     UI.Notify(String.Format("Loading actor from {0}", path));
     var reader = new XmlTextReader(path);
     while (reader.Read())
     {
         if (reader.Name == "SetPlayerModel")
         {
             ped_data.ChangePlayerModel(_pedhash[reader.GetAttribute("name")]);
         }
         else if (reader.Name == "SetSlotValue")
         {
             var key = new SlotKey(reader);
             var val = new SlotValue(reader);
             ped_data.SetSlotValue(Game.Player.Character, key, val);
         }
     }
 }
Example #23
0
 public void AddClearAppearanceToMenu(UIMenu menu)
 {
     Action Clear = () =>
     {
         var ped = Game.Player.Character;
         foreach (SlotType slot_type in Enum.GetValues(typeof(SlotType)))
             for (int slot_id = 0; slot_id < PedData.GetNumId(slot_type); slot_id++)
             {
                 var slot_key = new SlotKey(slot_type, slot_id);
                 ped_data.ClearSlot(ped, slot_key);
             }
     };
     var clearitem = new UIMenuItem("Clear Appearance");
     AddItem(menu, clearitem, OnSelect: Clear);
 }
        public void RandomizeTrees(Random random, Permutation permutation, SkillSplitter.Assignment split)
        {
            // >= 700: prosthetics
            // < 400: skills before mushin
            GameEditor editor = game.Editor;
            PARAM      param  = game.Params["SkillParam"];

            // Orderings for skills which completely supersede each other. (For prosthetics, just use their natural id ordering)
            Dictionary <int, int> skillOrderings = new Dictionary <int, int>
            {
                [110] = 111,  // Nightjar slash
                [210] = 211,  // Ichimonji
                [310] = 311,  // Praying Strikes
            };
            Dictionary <int, ItemKey> texts = new Dictionary <int, ItemKey>
            {
                [0] = game.ItemForName("Shinobi Esoteric Text"),
                [1] = game.ItemForName("Prosthetic Esoteric Text"),
                [2] = game.ItemForName("Ashina Esoteric Text"),
                [3] = game.ItemForName("Senpou Esoteric Text"),
                // [4] = game.ItemForName("Mushin Esoteric Text"),
            };
            SortedDictionary <ItemKey, string> names = game.Names();

            string descName(int desc)
            {
                return(names[new ItemKey(ItemType.WEAPON, desc)]);
            }

            Dictionary <int, SkillData>     allData    = new Dictionary <int, SkillData>();
            Dictionary <int, SkillSlot>     allSlots   = new Dictionary <int, SkillSlot>();
            Dictionary <ItemKey, SkillData> skillItems = new Dictionary <ItemKey, SkillData>();
            List <SkillData> skills          = new List <SkillData>();
            List <SkillSlot> skillSlots      = new List <SkillSlot>();
            List <SkillData> prosthetics     = new List <SkillData>();
            List <SkillSlot> prostheticSlots = new List <SkillSlot>();

            bool explain = false;

            foreach (PARAM.Row r in param.Rows)
            {
                SkillData data = new SkillData
                {
                    ID           = (int)r.ID,
                    Item         = (int)r["SkilLDescriptionId"].Value,
                    Equip        = (int)r["Unk1"].Value,
                    Flag         = (int)r["EventFlagId"].Value,
                    Placeholder  = (int)r["Unk5"].Value,
                    SpEffects    = new[] { (int)r["Unk2"].Value, (int)r["Unk3"].Value },
                    EmblemChange = (byte)r["Unk10"].Value != 0,
                };
                data.Key             = new ItemKey(ItemType.WEAPON, data.Item);
                skillItems[data.Key] = data;
                SkillSlot slot = new SkillSlot
                {
                    ID   = (int)r.ID,
                    Col  = (short)r["MenuDisplayPositionIndexXZ"].Value,
                    Row  = (short)r["MenuDisplayPositionIndexY"].Value,
                    Text = data.ID < 400 && texts.TryGetValue((byte)r["Unk7"].Value, out ItemKey text) ? text : null,
                };
                if (explain)
                {
                    Console.WriteLine($"{r.ID}: {data.Item}, {data.Equip}, {descName(data.Item)}");
                }
                if (data.ID < 400)
                {
                    skills.Add(data);
                    skillSlots.Add(slot);
                }
                else if (data.ID >= 700)
                {
                    prosthetics.Add(data);
                    prostheticSlots.Add(slot);
                }
                allData[data.ID]  = data;
                allSlots[slot.ID] = slot;
            }
            void applyData(PARAM.Row r, SkillData data)
            {
                r["SkilLDescriptionId"].Value = data.Item;
                r["EventFlagId"].Value        = data.Flag;
                r["Unk1"].Value  = data.Equip;
                r["Unk2"].Value  = data.SpEffects[0];
                r["Unk3"].Value  = data.SpEffects[0];
                r["Unk5"].Value  = data.Placeholder;
                r["Unk10"].Value = (byte)(data.EmblemChange ? 1 : 0);
            }

            Shuffle(random, skills);
            Shuffle(random, skillSlots);
            Shuffle(random, prosthetics);
            Shuffle(random, prostheticSlots);

            // Skills rando
            if (split == null)
            {
                Dictionary <ItemKey, string> textWeight    = new Dictionary <ItemKey, string>();
                Dictionary <ItemKey, string> textLocations = texts.Values.ToDictionary(t => t, t => {
                    SlotKey target    = permutation.GetFiniteTargetKey(t);
                    textWeight[t]     = permutation.GetLogOrder(target);
                    SlotAnnotation sn = ann.Slot(data.Location(target).LocScope);
                    if (explain)
                    {
                        Console.WriteLine($"{game.Name(t)} in {sn.Area} - {sn.Text}. Lateness {(permutation.ItemLateness.TryGetValue(t, out double val) ? val : -1)}");
                    }
                    return(sn.Area);
                });
Example #25
0
    public int GetNumIndex4(Ped ped, SlotKey key)
    {
        var slot_value = GetSlotValue(key);

        return(GetNumIndex4(ped, key, slot_value.index1, slot_value.index2, slot_value.index3));
    }
Example #26
0
 public SlotValue GetSlotValue(SlotKey slot)
 {
     if (data.ContainsKey(slot))
         return data[slot];
     else
         return new SlotValue(0, 0, 0, 0);
 }
Example #27
0
    public static int GetNumIndex2(Ped ped, SlotKey key, int index1)
    {
        switch (key.typ)
        {
            case SlotType.CompVar:
                // drawable_id = index1
                return Function.Call<int>(
                    Hash.GET_NUMBER_OF_PED_TEXTURE_VARIATIONS, ped.Handle, key.id, index1);
            case SlotType.Prop:
                if (index1 == 0)
                    // no prop, so no textures
                    return 1;
                else
                {
                    // drawable_id = index1 - 1
                    var num = Function.Call<int>(
                        Hash.GET_NUMBER_OF_PED_PROP_TEXTURE_VARIATIONS, ped.Handle, key.id, index1 - 1);
                    // sometimes returns -1 or 0 if there are no textures
                    return (num <= 0) ? 1 : num;

                }
            case SlotType.HeadOverlay:
                if (index1 == 0)
                    // clear overlay
                    return 1;
                else
                    // opacity: 0 -> 1.0, 7 -> 0.125, in steps of 0.125
                    return 7;
            case SlotType.Parent:
                return (
                    Function.Call<int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 0) +
                    Function.Call<int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 1) +
                    Function.Call<int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 2) +
                    Function.Call<int>(Hash._GET_NUM_PARENT_PEDS_OF_TYPE, 3)
                    );
            default:
                return 1;
        }
    }
Example #28
0
 public void SetSlotValue(Ped ped, SlotKey slot_key, SlotValue slot_value)
 {
     // store it
     if (slot_value.index1 == 0 && slot_value.index2 == 0 && slot_value.index3 == 0 && slot_value.index4 == 0)
         data.Remove(slot_key);
     else
         data[slot_key] = slot_value;
     // change game state accordingly
     switch (slot_key.typ)
     {
         case SlotType.CompVar:
             Function.Call(
                 Hash.SET_PED_COMPONENT_VARIATION,
                 ped.Handle,
                 slot_key.id, slot_value.index1, slot_value.index2, 0);
             if (slot_key.id == 2) // hair: use index3 and index4
                 Function.Call(Hash._SET_PED_HAIR_COLOR, ped.Handle, slot_value.index3, slot_value.index4);
             break;
         case SlotType.Prop:
             if (slot_value.index1 == 0)
                 Function.Call(Hash.CLEAR_PED_PROP, ped.Handle, slot_key.id);
             else
                 Function.Call(Hash.SET_PED_PROP_INDEX, ped.Handle, slot_key.id, slot_value.index1 - 1, slot_value.index2, true);
             break;
         case SlotType.HeadOverlay:
             if (slot_value.index1 == 0)
                 Function.Call(Hash.SET_PED_HEAD_OVERLAY, ped.Handle, slot_key.id, 0, 0.0f);
             else
             {
                 // index2 is opacity
                 Function.Call(Hash.SET_PED_HEAD_OVERLAY, ped.Handle, slot_key.id, slot_value.index1 - 1, (8 - slot_value.index2) / 8.0f);
                 // takes color (index3) and highlight color (index4), but the latter is ignored
                 // so use index3 for both colors just in case
                 var color_type = mp_head_overlay_color_type[slot_key.id];
                 switch (color_type)
                 {
                     case ColorType.Hair:
                         Function.Call(Hash._SET_PED_HEAD_OVERLAY_COLOR, ped.Handle, slot_key.id, 1, slot_value.index3, slot_value.index3);
                         break;
                     case ColorType.Makeup:
                         Function.Call(Hash._SET_PED_HEAD_OVERLAY_COLOR, ped.Handle, slot_key.id, 2, slot_value.index3, slot_value.index3);
                         break;
                 }
             }
             break;
         case SlotType.Eye:
             Function.Call(Hash._SET_PED_EYE_COLOR, ped.Handle, slot_value.index1);
             break;
         case SlotType.Parent:
             // get current parent info
             var par = GetSlotValue(new SlotKey(SlotType.Parent, 0));
             var shape1 = par.index1;
             var shape2 = par.index2;
             var skin1 = par.index1;
             var skin2 = par.index2;
             float shapemix = par.index3 / 8.0f;
             float skinmix = par.index4 / 8.0f;
             Function.Call(
                 Hash.SET_PED_HEAD_BLEND_DATA, ped.Handle,
                 shape1, shape2, 0, skin1, skin2, 0, shapemix, skinmix, 0.0f);
             break;
     }
 }
Example #29
0
    public void SetSlotValue(Ped ped, SlotKey slot_key, SlotValue slot_value)
    {
        // store it
        if (slot_value.index1 == 0 && slot_value.index2 == 0 && slot_value.index3 == 0 && slot_value.index4 == 0)
        {
            data.Remove(slot_key);
        }
        else
        {
            data[slot_key] = slot_value;
        }
        // change game state accordingly
        switch (slot_key.typ)
        {
        case SlotType.CompVar:
            Function.Call(
                Hash.SET_PED_COMPONENT_VARIATION,
                ped.Handle,
                slot_key.id, slot_value.index1, slot_value.index2, 0);
            if (slot_key.id == 2)     // hair: use index3 and index4
            {
                Function.Call(Hash._SET_PED_HAIR_COLOR, ped.Handle, slot_value.index3, slot_value.index4);
            }
            break;

        case SlotType.Prop:
            if (slot_value.index1 == 0)
            {
                Function.Call(Hash.CLEAR_PED_PROP, ped.Handle, slot_key.id);
            }
            else
            {
                Function.Call(Hash.SET_PED_PROP_INDEX, ped.Handle, slot_key.id, slot_value.index1 - 1, slot_value.index2, true);
            }
            break;

        case SlotType.HeadOverlay:
            if (slot_value.index1 == 0)
            {
                Function.Call(Hash.SET_PED_HEAD_OVERLAY, ped.Handle, slot_key.id, 0, 0.0f);
            }
            else
            {
                // index2 is opacity
                Function.Call(Hash.SET_PED_HEAD_OVERLAY, ped.Handle, slot_key.id, slot_value.index1 - 1, (8 - slot_value.index2) / 8.0f);
                // takes color (index3) and highlight color (index4), but the latter is ignored
                // so use index3 for both colors just in case
                var color_type = mp_head_overlay_color_type[slot_key.id];
                switch (color_type)
                {
                case ColorType.Hair:
                    Function.Call(Hash._SET_PED_HEAD_OVERLAY_COLOR, ped.Handle, slot_key.id, 1, slot_value.index3, slot_value.index3);
                    break;

                case ColorType.Makeup:
                    Function.Call(Hash._SET_PED_HEAD_OVERLAY_COLOR, ped.Handle, slot_key.id, 2, slot_value.index3, slot_value.index3);
                    break;
                }
            }
            break;

        case SlotType.Eye:
            Function.Call(Hash._SET_PED_EYE_COLOR, ped.Handle, slot_value.index1);
            break;

        case SlotType.Parent:
            // get current parent info
            var   par      = GetSlotValue(new SlotKey(SlotType.Parent, 0));
            var   shape1   = par.index1;
            var   shape2   = par.index2;
            var   skin1    = par.index1;
            var   skin2    = par.index2;
            float shapemix = par.index3 / 8.0f;
            float skinmix  = par.index4 / 8.0f;
            Function.Call(
                Hash.SET_PED_HEAD_BLEND_DATA, ped.Handle,
                shape1, shape2, 0, skin1, skin2, 0, shapemix, skinmix, 0.0f);
            break;
        }
    }
Example #30
0
 public ItemLocation Location(SlotKey key)
 {
     return(Data[key.Item].Locations[key.Scope]);
 }
Example #31
0
        public void Write(RandomizerOptions opt, Permutation permutation)
        {
            // Overall: 9020, and 9200 to 9228
            // Exclude 9209 and 9215 and 9228 bc it's useful
            // Also, 9221 9223 9225 are 'purchase to read'
            // Can replace everything in quotes
            // Or after the first :\n
            // Or after the last \n\n
            // If the message ends with '               -?Name' can leave that. space or ideographic space (3000). multiple whitespace either way
            //
            // Alternatively: replace individual text, like 'moon-view tower'
            // defeat a named enemy
            // defeat a formidable foe
            // defeat a powerful enemy
            FMG itemDesc  = game.ItemFMGs["アイテム説明"];
            FMG eventText = game.MenuFMGs["イベントテキスト"];

            if (opt["writehints"])
            {
                List <int> eventIds = new List <int>
                {
                    12000000, 12000331, 12000021, 12000261, 12000275, 12000321, 12000285,
                    12000241, 12000011, 12000311, 12000231, 12000291, 12000341,
                };
                eventIds.Sort();
                HintData write = new HintData();
                void createHint(int id, string name, string text)
                {
                    Hint hint = new Hint
                    {
                        ID       = id,
                        Name     = name,
                        Versions = new List <HintTemplate>
                        {
                            new HintTemplate
                            {
                                Type = "default",
                                Text = text.Split('\n').ToList(),
                            }
                        },
                    };

                    write.Hints.Add(hint);
                }

                foreach (KeyValuePair <ItemKey, string> entry in game.Names())
                {
                    ItemKey key = entry.Key;
                    if (!(key.Type == ItemType.GOOD && (key.ID == 9020 || key.ID >= 9200 && key.ID <= 9228)))
                    {
                        continue;
                    }
                    createHint(key.ID, entry.Value, itemDesc[key.ID]);
                }
                foreach (int id in eventIds)
                {
                    createHint(id, null, eventText[id]);
                }
                ISerializer serializer = new SerializerBuilder().DisableAliases().Build();
                using (var writer = File.CreateText("hints.txt"))
                {
                    serializer.Serialize(writer, write);
                }
                return;
            }

            IDeserializer deserializer = new DeserializerBuilder().Build();
            HintData      hints;

            using (var reader = File.OpenText("dists/Base/hints.txt"))
            {
                hints = deserializer.Deserialize <HintData>(reader);
            }

            // Preprocess some items
            Dictionary <HintType, TypeHint>            typeNames    = hints.Types.ToDictionary(e => e.Name, e => e);
            Dictionary <ItemCategory, List <ItemKey> > categories   = ((ItemCategory[])Enum.GetValues(typeof(ItemCategory))).ToDictionary(e => e, e => new List <ItemKey>());
            Dictionary <ItemCategory, string>          categoryText = new Dictionary <ItemCategory, string>();

            foreach (ItemHint cat in hints.ItemCategories)
            {
                if (cat.Includes != null)
                {
                    categories[cat.Name].AddRange(cat.Includes.Split(' ').Select(i => ann.Items.TryGetValue(i, out ItemKey key) ? key : throw new Exception($"Unrecognized name {i}")));
                }
                if (cat.IncludesName != null)
                {
                    categories[cat.Name].AddRange(phraseRe.Split(cat.IncludesName).Select(i => game.ItemForName(i)));
                }
                if (cat.Text != null)
                {
                    categoryText[cat.Name] = cat.Text;
                }
            }
            if (opt["earlyhirata"])
            {
                categories[ItemCategory.ExcludeHints].Add(ann.Items["younglordsbellcharm"]);
            }
            categories[ItemCategory.ExcludeHints].AddRange(permutation.NotRequiredKeyItems);

            // TODO: Exclude non-technically-required items... calculate this in key item permutations
            List <ItemKey> allItems = permutation.KeyItems.ToList();

            if (opt["norandom_skills"])
            {
                categories[ItemCategory.ImportantTool].Clear();
            }
            else
            {
                allItems.AddRange(categories[ItemCategory.ImportantTool]);
            }
            allItems.AddRange(categories[ItemCategory.HintFodder]);

            Dictionary <string, ItemHintName> specialItemNames = hints.ItemNames.ToDictionary(n => n.Name, n => n);

            // Process areas
            Dictionary <string, AreaHint> areas = new Dictionary <string, AreaHint>();
            HashSet <string> gameAreas          = new HashSet <string>(ann.Areas.Keys);

            List <string> getAreasForName(string names)
            {
                List <string> nameList = new List <string>();

                foreach (string name in names.Split(' '))
                {
                    if (name.EndsWith("*"))
                    {
                        string        prefix   = name.Substring(0, name.Length - 1);
                        List <string> matching = gameAreas.Where(n => n.StartsWith(prefix)).ToList();
                        if (matching.Count == 0)
                        {
                            throw new Exception($"Unrecognized area in hint config: {name}");
                        }
                        nameList.AddRange(matching);
                    }
                    else
                    {
                        if (!gameAreas.Contains(name))
                        {
                            throw new Exception($"Unrecognized area in hint config: {name}");
                        }
                        nameList.Add(name);
                    }
                }
                return(nameList);
            }

            foreach (AreaHint area in hints.Areas)
            {
                if (area.Name == null || area.Includes == null)
                {
                    throw new Exception($"Missing data in area hint grouping {area.Name}");
                }
                areas[area.Name] = area;
                area.Areas.UnionWith(getAreasForName(area.Includes));
                if (area.Excludes != null)
                {
                    area.Areas.ExceptWith(getAreasForName(area.Excludes));
                }
                if (area.LaterIncludes != null)
                {
                    area.LaterAreas.UnionWith(getAreasForName(area.LaterIncludes));
                    if (!area.LaterAreas.IsSubsetOf(area.Areas))
                    {
                        throw new Exception($"Error in hint config: later areas of {area.Name} are not a subset of all areas");
                    }
                }
                area.EarlyAreas.UnionWith(area.Areas.Except(area.LaterAreas));
                if (area.Parent != null)
                {
                    if (!areas.TryGetValue(area.Parent, out AreaHint parent))
                    {
                        throw new Exception($"Error in hint config: parent of {area.Name} does not exist: {area.Parent}");
                    }
                    area.Parents.Add(parent);
                    area.Parents.UnionWith(parent.Parents);
                }
                if (area.Present != null)
                {
                    area.Types = area.Present.Split(' ').Select(t => (HintType)Enum.Parse(typeof(HintType), t)).ToList();
                }
            }

            bool printText = opt["hinttext"];

            // Process items to search for
            List <ItemCategory> categoryOverrides = new List <ItemCategory> {
                ItemCategory.RequiredKey, ItemCategory.RequiredAbility, ItemCategory.ImportantTool, ItemCategory.HintFodder
            };
            HashSet <string> chests = new HashSet <string> {
                "o005300", "o005400", "o255300"
            };
            List <Placement> placements = new List <Placement>();
            Dictionary <ItemKey, Placement> itemPlacement = new Dictionary <ItemKey, Placement>();

            foreach (ItemKey key in allItems)
            {
                if (categories[ItemCategory.ExcludeHints].Contains(key))
                {
                    continue;
                }
                if (!permutation.SkillAssignment.TryGetValue(key, out ItemKey lookup))
                {
                    lookup = key;
                }
                SlotKey      targetKey = permutation.GetFiniteTargetKey(lookup);
                ItemLocation itemLoc   = data.Location(targetKey);
                if (!ann.Slots.TryGetValue(itemLoc.LocScope, out SlotAnnotation slot))
                {
                    continue;
                }
                ItemCategory category = ItemCategory.RequiredItem;
                foreach (ItemCategory cat in categoryOverrides)
                {
                    // Use the last applicable category
                    if (categories[cat].Contains(key))
                    {
                        category = cat;
                    }
                }
                List <HintType> types = new List <HintType>();
                if (slot.HasTag("boss") || slot.HasTag("bosshint"))
                {
                    types.Add(HintType.Boss);
                    types.Add(HintType.Enemy);
                }
                else if (slot.HasTag("miniboss"))
                {
                    types.Add(HintType.Miniboss);
                    types.Add(HintType.Enemy);
                }
                else if (slot.HasTag("enemyhint"))
                {
                    types.Add(HintType.Enemy);
                }
                else if (slot.HasTag("carp"))
                {
                    types.Add(HintType.Carp);
                }
                else if (itemLoc.Keys.Any(k => k.Type == LocationKey.LocationType.SHOP && k.ID / 100 != 11005))
                {
                    types.Add(HintType.Shop);
                }
                else
                {
                    if (slot.Area.EndsWith("_underwater") || slot.HasTag("underwater"))
                    {
                        types.Add(HintType.Underwater);
                    }
                    if (itemLoc.Keys.Any(k => k.Type == LocationKey.LocationType.LOT && k.Entities.Any(e => chests.Contains(e.ModelName))))
                    {
                        types.Add(HintType.Chest);
                    }
                    types.Add(HintType.Treasure);
                }

                string    name      = game.Name(key);
                Placement placement = new Placement
                {
                    Item         = key,
                    FullName     = name,
                    Category     = category,
                    LateEligible = categories[ItemCategory.LatenessHints].Contains(key),
                    Important    = category != ItemCategory.HintFodder,
                    Area         = slot.Area,
                    Types        = types,
                };
                if (placement.Important)
                {
                    placements.Add(placement);
                }
                itemPlacement[key] = placement;
                if (printText)
                {
                    Console.WriteLine(placement);
                }
            }

            foreach (Hint hint in hints.Hints)  // Lovely
            {
                hint.Types = hint.Versions.Where(v => v.Type != "default").ToDictionary(v => v.Type, v => v);
            }

            // Classify early and late areas
            HashSet <string> early = new HashSet <string>(permutation.IncludedAreas.Where(e => !e.Value.Contains("ashinacastle")).Select(e => e.Key));
            HashSet <string> late  = new HashSet <string>(permutation.IncludedAreas.Where(e => e.Value.Contains("fountainhead_bridge")).Select(e => e.Key));

            // Start hints
            Random      random  = new Random((int)opt.Seed);
            List <Hint> sources = hints.Hints.Where(s => s.Types.Any(e => e.Key != "default")).ToList();

            Shuffle(random, sources);
            sources = sources.OrderBy(s => (s.HasInfix("bad") && s.Types.ContainsKey("hint")) ? 1 : 0).ToList();
            string choose(List <string> items)
            {
                return(items.Count == 1 ? items[0] : Choice(random, items));
            }

            // Process all hint types. There are 20 item locations in the entire game, plus 13 fixed texts, for a total of 33
            Regex format = new Regex(@"\(([^\)]*)\)");

            if (printText)
            {
                Console.WriteLine($"No hint items: {string.Join(", ", categories[ItemCategory.ExcludeHints].Select(k => game.Name(k)))}");
            }
            void addHint(Hint hint, HintTemplate t, Placement mainPlacement, Placement otherPlacement = null)
            {
                string text     = printText ? string.Join(" ", t.Text.Select(l => l.Trim())) : string.Join("\n", t.Text);
                bool   positive = !t.Type.Contains("bad");

                foreach (Match m in format.Matches(text))
                {
                    string    variable = m.Groups[1].Value;
                    string[]  parts    = variable.Split('_');
                    string    kind     = parts[0].ToLowerInvariant();
                    bool      upper    = char.IsUpper(parts[0][0]);
                    string    subkind  = parts.Length > 1 ? parts[1] : null;
                    string    value;
                    Placement placement = kind == "location2" && otherPlacement != null ? otherPlacement : mainPlacement;
                    if (kind == "item")
                    {
                        if (positive)
                        {
                            if (placement.LateHint || !categoryText.ContainsKey(placement.Category))
                            {
                                value = placement.FullName;
                                if (specialItemNames.TryGetValue(value, out ItemHintName vagueName))
                                {
                                    value = choose(vagueName.GetNames());
                                }
                            }
                            else
                            {
                                value = categoryText[placement.Category];
                            }
                        }
                        else
                        {
                            // Shouldn't be used in this context, but fall back
                            value = "nothing";
                        }
                    }
                    else if (kind == "type")
                    {
                        HintType type = placement.Types.FirstOrDefault();
                        value = choose(typeNames[placement.Types[0]].GetNames(subkind));
                    }
                    else if (kind == "location" || kind == "location2")
                    {
                        bool prep;
                        if (subkind == null)
                        {
                            prep = false;
                        }
                        else if (subkind == "preposition")
                        {
                            prep = true;
                        }
                        else
                        {
                            throw new Exception($"Unknown hint config variable {variable}");
                        }
                        if (positive || placement.Types.Count == 0)
                        {
                            value = placement.AreaHint == null ? (positive ? "somewhere" : "anywhere") : choose(placement.AreaHint.GetNames(prep));
                        }
                        else
                        {
                            if (prep || placement.AreaHint != null)
                            {
                                value = (prep ? "from " : "") + choose(typeNames[placement.Types[0]].GetNames("noun")) + " " + (placement.AreaHint == null ? (positive ? "somewhere" : "anywhere") : choose(placement.AreaHint.GetNames(true)));
                            }
                            else
                            {
                                value = value = choose(typeNames[placement.Types[0]].GetNames("gerund"));
                            }
                        }
                    }
                    else
                    {
                        throw new Exception($"Unknown hint variable {variable}");
                    }
                    if (upper)
                    {
                        value = value[0].ToString().ToUpperInvariant() + value.Substring(1);
                    }
                    text = text.Replace($"({variable})", value);
                }
                if (printText)
                {
                    Console.WriteLine(text + "\n");
                }
                if (hint.ID < 10000)
                {
                    itemDesc[hint.ID] = text;
                }
                else
                {
                    eventText[hint.ID] = text;
                }
            }

            AreaHint mostSpecificArea(string name)
            {
                AreaHint selected = null;

                foreach (AreaHint area in hints.Areas)
                {
                    if (area.EarlyAreas.Contains(name))
                    {
                        if (selected == null || area.Parents.Contains(selected))
                        {
                            selected = area;
                        }
                    }
                }
                return(selected);
            }

            T pop <T>(List <T> list)
            {
                T ret = list[list.Count - 1];

                list.RemoveAt(list.Count - 1);
                return(ret);
            }

            // In priority order:
            // Item hints: Find item at (type) and (location). Always filled in. 1 of these.
            HashSet <ItemKey> exactKey = new HashSet <ItemKey>();

            foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("itemhint")).ToList())
            {
                HintTemplate t = hint.Types["itemhint"];
                if (!ann.Items.TryGetValue(t.Req, out ItemKey key))
                {
                    throw new Exception($"Unrecognized name {t.Req}");
                }
                if (!itemPlacement.TryGetValue(key, out Placement placement))
                {
                    continue;
                }
                exactKey.Add(key);
                placement          = placement.Copy();
                placement.AreaHint = mostSpecificArea(placement.Area);
                addHint(hint, t, placement);
                sources.Remove(hint);
            }

            // Location hints: Find (item/item category/nothing) at (location). Always filled in. 2 of these.
            foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("locationhint")).ToList())
            {
                HintTemplate     t        = hint.Types["locationhint"];
                List <string>    reqAreas = t.Req.Split(' ').ToList();
                List <Placement> places   = placements.Where(p => reqAreas.Contains(p.Area)).ToList();
                if (places.Count == 0 && hint.Types.TryGetValue("locationbadhint", out HintTemplate t2))
                {
                    addHint(hint, t2, null);
                }
                else
                {
                    Placement placement = places[0].Copy();
                    addHint(hint, t, placement);
                }
                sources.Remove(hint);
            }

            // Global negative hint: There is nothing at (type). Always include as many as applicable for likely types, treasure/chest/boss/miniboss/enemy/underwater/shop (but more like 1-2)
            List <HintType> present = placements.SelectMany(p => p.Types).Distinct().ToList();
            List <HintType> absent  = new List <HintType>
            {
                HintType.Treasure, HintType.Chest, HintType.Boss, HintType.Miniboss, HintType.Enemy, HintType.Underwater, HintType.Shop
            }
            .Except(present).ToList();

            foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("badhint")).ToList())
            {
                if (absent.Count == 0)
                {
                    break;
                }
                HintType     type = pop(absent);
                HintTemplate t    = hint.Types["badhint"];
                addHint(hint, t, new Placement
                {
                    Types = new List <HintType> {
                        type
                    },
                });
                sources.Remove(hint);
            }

            // Positive hint: There is (item/item category) at (type) in (location). Include one per key item, up to 13, and special items, currently 3.
            List <Placement> toPlace = placements.Where(p => !exactKey.Contains(p.Item)).ToList();

            foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("hint")).ToList())
            {
                if (toPlace.Count == 0)
                {
                    break;
                }
                HintTemplate t         = hint.Types["hint"];
                Placement    placement = pop(toPlace);
                placement          = placement.Copy();
                placement.AreaHint = mostSpecificArea(placement.Area);
                addHint(hint, t, placement);
                sources.Remove(hint);
            }

            // Lateness hint: There is (item) at (type) (late/early). Include one per location item, up to 6.
            toPlace = placements.Where(p => p.LateEligible).ToList();
            foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("hint")).ToList())
            {
                if (toPlace.Count == 0)
                {
                    break;
                }
                HintTemplate t         = hint.Types["hint"];
                Placement    placement = pop(toPlace);
                placement          = placement.Copy();
                placement.LateHint = true;
                string info = early.Contains(placement.Area) ? "an early game location" : (late.Contains(placement.Area) ? "a late game location" : "a mid game location");
                string prep = early.Contains(placement.Area) ? "in the early game" : (late.Contains(placement.Area) ? "in the late game" : "in the mid game");
                placement.AreaHint = new AreaHint {
                    Name = info, Vague = info, VaguePrep = prep
                };
                addHint(hint, t, placement);
                sources.Remove(hint);
            }

            // So far, around 25 hints created, with ~10 left to go. At this point, pick randomly from either of these categories.
            List <AreaHint> withoutRedundantChildren(List <AreaHint> allAreas)
            {
                return(allAreas.Where(area => area.Parents.Count == 0 || !area.Parents.Any(p => allAreas.Contains(p))).ToList());
            }

            List <AreaHint> areasWithoutPlacements(HintType type = HintType.None)
            {
                HashSet <string> importantAreas = new HashSet <string>(placements.Where(p => type == HintType.None || p.Types.Contains(type)).Select(p => p.Area));

                return(hints.Areas.Where(h => (type == HintType.None || h.Types.Contains(type)) && !importantAreas.Overlaps(h.Areas)).ToList());
            }

            // Area negative hint: There is nothing in (location). Can be included for all such areas, but eliminate a hint if its parent also applies.
            Dictionary <HintType, List <AreaHint> > negativeHints = new Dictionary <HintType, List <AreaHint> >();
            List <AreaHint> unimportantAreas = areasWithoutPlacements();

            negativeHints[HintType.None] = withoutRedundantChildren(unimportantAreas);
            foreach (HintType noType in new[] { HintType.Boss, HintType.Miniboss, HintType.Treasure })
            {
                negativeHints[noType] = withoutRedundantChildren(areasWithoutPlacements(noType)).Except(unimportantAreas).ToList();
            }
            negativeHints[HintType.Enemy] = new List <AreaHint>();
            foreach (AreaHint allEnemy in negativeHints[HintType.Boss].Intersect(negativeHints[HintType.Miniboss]).ToList())
            {
                // Add 'no powerful enemy' hints if no boss and no miniboss.
                negativeHints[HintType.Enemy].Add(allEnemy);
                negativeHints[HintType.Boss].Remove(allEnemy);
                negativeHints[HintType.Miniboss].Remove(allEnemy);
            }
            List <Placement> negatives = new List <Placement>();

            foreach (KeyValuePair <HintType, List <AreaHint> > entry in negativeHints)
            {
                List <HintType> types = entry.Key == HintType.None ? new List <HintType>() : new List <HintType> {
                    entry.Key
                };
                foreach (AreaHint area in entry.Value)
                {
                    negatives.Add(new Placement
                    {
                        AreaHint = area,
                        Types    = types,
                    });
                }
            }
            // Area type hint: There is nothing at (type) in (location). Include this for treasure and for miniboss/boss/enemy, eliminating when parent (or parent type) applies.
            Shuffle(random, negatives);
            negatives = negatives.OrderBy(a => a.AreaHint.AreaRank).ToList();
            // If there are at least 4 negative hints, allow up to 3 of the remainder to become fodder.
            List <Hint> negativeHintTemplates = sources.Where(s => s.Types.ContainsKey("badhint") || s.Types.ContainsKey("badhint2")).ToList();

            if (negatives.Count >= 4 && negativeHintTemplates.Count > 4)
            {
                int cutoffIndex = Math.Max(negativeHintTemplates.Count - 3, 4);
                negativeHintTemplates.RemoveRange(cutoffIndex, negativeHintTemplates.Count - cutoffIndex);
            }
            foreach (Hint hint in negativeHintTemplates)
            {
                if (negatives.Count == 0)
                {
                    break;
                }
                HintTemplate t              = hint.Types.ContainsKey("badhint") ? hint.Types["badhint"] : hint.Types["badhint2"];
                Placement    placement      = pop(negatives);
                Placement    otherPlacement = t.Type == "badhint2" && negatives.Count > 0 ? pop(negatives) : null;
                addHint(hint, t, placement, otherPlacement);
                sources.Remove(hint);
            }

            if (printText)
            {
                Console.WriteLine($"{sources.Count} remaining hints: [{string.Join(", ", sources.Select(s => string.Join("/", s.Types.Keys)))}]");
            }
            if (sources.Count > 0)
            {
                // At this point, pull in misc hints for somewhat useful items
                toPlace = categories[ItemCategory.HintFodder].Select(k => itemPlacement[k]).ToList();
                Shuffle(random, toPlace);
                foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("hint")).ToList())
                {
                    if (toPlace.Count == 0)
                    {
                        break;
                    }
                    HintTemplate t         = hint.Types["hint"];
                    Placement    placement = pop(toPlace);
                    placement          = placement.Copy();
                    placement.AreaHint = mostSpecificArea(placement.Area);
                    addHint(hint, t, placement);
                    sources.Remove(hint);
                }
            }

            // Need to figure out which items are strictly required to beat the game
            // Also, for bad hints, find all strictly required items plus key items
            // List all locations which can have hints scoped to them. The entirety of Sunken Valley, or just lower/upper, or burrow
            // Most key items get minimum specificity
            // Required side area items will get early/lateness specificity when that applies
            // Skills/prosthetics will get maximum specificity, maybe even two for Mikiri

            // Mortal Blade is excluded, since it has its own explicit hint
            // Young Lord Bell Charm is excluded if earlyhirata
        }