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(); }
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); } }
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); }
public SlotValue GetSlotValue(SlotKey slot) { if (data.ContainsKey(slot)) { return(data[slot]); } else { return(new SlotValue(0, 0, 0, 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); }
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); }
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); } }
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); } }
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); }
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); }
// 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); }
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); } } }
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); }
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); }
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(); }
// 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; }
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; }
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; }
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; } }
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); } }; }
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); } }; }
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); });
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)); }
public SlotValue GetSlotValue(SlotKey slot) { if (data.ContainsKey(slot)) return data[slot]; else return new SlotValue(0, 0, 0, 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; } }
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; } }
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; } }
public ItemLocation Location(SlotKey key) { return(Data[key.Item].Locations[key.Scope]); }
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 }