├── .github └── FUNDING.yml ├── Code ├── Mod │ ├── HighResolutionDateTime.cs │ ├── InputConfig.cs │ ├── ModData.cs │ └── UserXpMod.cs └── ModifiedScripts.diff ├── Data ├── ActionId.csv ├── KeyboardKeycode.csv └── KeyboardMap.xml └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: moartis 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /Code/Mod/HighResolutionDateTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Mod 5 | { 6 | public static class HighResolutionTime 7 | { 8 | public static bool IsAvailable { get; private set; } 9 | 10 | [DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi)] 11 | private static extern void GetSystemTimePreciseAsFileTime(out long filetime); 12 | 13 | public static long Time 14 | { 15 | get 16 | { 17 | if (!IsAvailable) 18 | { 19 | throw new InvalidOperationException( 20 | "High resolution clock isn't available."); 21 | } 22 | 23 | long filetime; 24 | GetSystemTimePreciseAsFileTime(out filetime); 25 | 26 | return filetime; 27 | } 28 | } 29 | 30 | static HighResolutionTime() 31 | { 32 | try 33 | { 34 | long filetime; 35 | GetSystemTimePreciseAsFileTime(out filetime); 36 | IsAvailable = true; 37 | } 38 | catch (EntryPointNotFoundException) 39 | { 40 | // Not running Windows 8 or higher. 41 | IsAvailable = false; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Code/Mod/InputConfig.cs: -------------------------------------------------------------------------------- 1 | using Rewired; 2 | using System; 3 | using System.IO; 4 | 5 | namespace Mod 6 | { 7 | 8 | [System.Serializable] 9 | public struct InputConfig 10 | { 11 | public string up; 12 | public string down; 13 | public string left; 14 | public string right; 15 | public string jump; 16 | public string quickAttack; 17 | public string heavyAttack; 18 | public string specialAttack; 19 | public string block; 20 | public string recruit; 21 | public string start; 22 | 23 | public int[] ToKeycodeArray() 24 | { 25 | return new int[] { 26 | ParseKeycode(up), 27 | ParseKeycode(down), 28 | ParseKeycode(left), 29 | ParseKeycode(right), 30 | ParseKeycode(start), 31 | ParseKeycode(quickAttack), 32 | ParseKeycode(heavyAttack), 33 | ParseKeycode(jump), 34 | ParseKeycode(specialAttack), 35 | ParseKeycode(block), 36 | ParseKeycode(recruit), 37 | ParseKeycode(jump), 38 | ParseKeycode(specialAttack) 39 | }; 40 | } 41 | 42 | private int ParseKeycode(string keycodeStr) 43 | { 44 | KeyboardKeyCode keyboardKeyCode; 45 | try 46 | { 47 | keyboardKeyCode = (KeyboardKeyCode)Enum.Parse(typeof(KeyboardKeyCode), keycodeStr); 48 | } 49 | catch (Exception e) 50 | { 51 | File.WriteAllText(string.Format(".\\{0}.txt", keycodeStr), e.Message); 52 | keyboardKeyCode = KeyboardKeyCode.None; 53 | } 54 | return (int)keyboardKeyCode; 55 | } 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /Code/Mod/ModData.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Mod 4 | { 5 | public enum DefaultCharacters 6 | { 7 | Misako, 8 | Kyoko, 9 | Kunio, 10 | Riki, 11 | None 12 | } 13 | 14 | [System.Serializable] 15 | public struct GameplayModifications 16 | { 17 | public bool activeRecruits; 18 | public bool quickComboOverride; 19 | public bool backAttackCombo; 20 | public bool backAttackAutoParry; 21 | public bool heavyAttackGuardBreak; 22 | public bool sharedRewards; 23 | public bool monkMode; 24 | public bool alwaysKeepWeapons; 25 | } 26 | 27 | [System.Serializable] 28 | public struct ModData 29 | { 30 | public bool allowQuickSkip; 31 | public bool startFromStartMenu; 32 | public bool skipStartMenu; 33 | 34 | public string interactActionId; 35 | public bool instantInteraction; 36 | public InputConfig[] playerOneInputConfigs; 37 | public InputConfig[] playerTwoInputConfigs; 38 | public bool forceXInput; 39 | public bool usePS4buttonPrompts; 40 | public bool displayProgressionInfo; 41 | public bool swapPhoneNavigationButtons; 42 | 43 | public int vSyncCount; 44 | public int targetFramerate; 45 | public bool useCustomFramelimiter; 46 | 47 | public bool displayDamageOnHit; 48 | public Color xpGetTextColor; 49 | public int xpGetTextFontSize; 50 | public Color hitDmgTextColor; 51 | public int hitDmgTextFontSizeMin; 52 | public int hitDmgTextFontSizeMax; 53 | 54 | public bool moreInfosInUseableShops; 55 | public bool loiterSelectedByDefault; 56 | public bool startSelectedByDefault; 57 | public bool noTutorialInNewGamePlus; 58 | public string characterByDefault; 59 | public bool displayMoveInputsInDojo; 60 | 61 | public bool unlockSecretCharactersOnceForAllSave; 62 | 63 | public bool fixMaxStaminaBug; 64 | 65 | public bool activateTrainingMode; 66 | public float enemiesScalingRatio; 67 | 68 | public bool useJsonLocalizationData; 69 | 70 | public GameplayModifications gameplayModifications; 71 | 72 | public LocalizationKey[] localizationKeys; 73 | 74 | public InputConfig GetInputConfig(int playerId) 75 | { 76 | if (playerId == 0) 77 | { 78 | return playerOneInputConfigs[0]; 79 | } 80 | else 81 | { 82 | return playerTwoInputConfigs[0]; 83 | } 84 | } 85 | 86 | public void SetInteractActionId(string actionId) 87 | { 88 | interactActionId = actionId; 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /Code/Mod/UserXpMod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using UnityEngine; 5 | using System.IO; 6 | using Rewired; 7 | using System.Linq; 8 | using RCG; 9 | 10 | namespace Mod 11 | { 12 | public class UserXpMod : MonoBehaviour 13 | { 14 | private static UserXpMod s_instance; 15 | 16 | public static UserXpMod Instance 17 | { 18 | get 19 | { 20 | if (s_instance == null) 21 | { 22 | GameObject go = new GameObject("UserXpMod"); 23 | s_instance = go.AddComponent(); 24 | s_instance.LoadModData(); 25 | DontDestroyOnLoad(go); 26 | } 27 | return s_instance; 28 | } 29 | } 30 | 31 | private int[] playerOneElementIdentifierIds = new int[] { 23, 19, 1, 4, 54, 7, 25, 8, 21, 9, 20, 8, 21 }; 32 | private int[] playerTwoElementIdentifierIds = new int[] { 89, 90, 92, 91, 116, 38, 39, 37, 40, 52, 51, 37, 40 }; 33 | 34 | private string kbMapXmlTemplate = "{26}{27}000000000-0000-0000-0000-000000000000true011{0}0false0{13}000true011{1}0false1{14}000true001{2}0false1{15}000true001{3}0false0{16}000true091{4}0false0{17}000true031{5}0false0{18}000true041{6}0false0{19}000true021{7}0false0{20}000true051{8}0false0{21}000true061{9}0false0{22}000true071{10}0false0{23}000true0111{11}0false0{24}000true0121{12}0false0{25}000true"; 35 | 36 | public ModData data { get { LoadModData(); return _data; } set { _data = value; } } 37 | 38 | public bool AreEnemiesInvicible { get; private set; } 39 | public bool AreEnemiesNightmare { get; private set; } 40 | public bool AreEnemiesPacifist { get; private set; } 41 | public bool AreEnemiesNoAi { get; private set; } 42 | public bool ArePlayersInvincible { get; private set; } 43 | 44 | public bool HasUsedQuickSkip { get; private set; } 45 | public bool HasSeenIntros { get; set; } 46 | 47 | private ModData _data; 48 | 49 | public bool hasData = false; 50 | 51 | private void SwitchDebugMenu() 52 | { 53 | if (UI_DebugMenu_Manager.Instance.Open) 54 | { 55 | UI_DebugMenu_Manager.Instance.CloseMenu(); 56 | } 57 | else 58 | { 59 | UI_DebugMenu_Manager.Instance.OpenMenu(); 60 | } 61 | } 62 | 63 | private void InputUpdate() 64 | { 65 | if (Input.GetKey(KeyCode.LeftShift) && Input.GetKeyDown(KeyCode.F1)) 66 | { 67 | SwitchDebugMenu(); 68 | return; 69 | } 70 | 71 | TrainingInputUpdate(); 72 | } 73 | 74 | private void TrainingInputUpdate() 75 | { 76 | if (data.activateTrainingMode == false || GameState.CurrentState != GameStates.Playing) 77 | return; 78 | 79 | bool hasInput = false; 80 | 81 | if (Input.GetKeyDown(KeyCode.F1)) 82 | { 83 | AreEnemiesInvicible = !AreEnemiesInvicible; 84 | DisplayCustomTextAbovePlayer(0, string.Format("Invicible enemies: {0}", AreEnemiesInvicible), false, AreEnemiesInvicible ? Color.green : Color.red, 120); 85 | hasInput = true; 86 | } 87 | 88 | if (Input.GetKeyDown(KeyCode.F2)) 89 | { 90 | AreEnemiesNightmare = !AreEnemiesNightmare; 91 | DisplayCustomTextAbovePlayer(0, string.Format("Nightmare enemies: {0}", AreEnemiesNightmare), false, AreEnemiesNightmare ? Color.green : Color.red, 120); 92 | hasInput = true; 93 | } 94 | 95 | if (Input.GetKeyDown(KeyCode.F3)) 96 | { 97 | AreEnemiesPacifist = !AreEnemiesPacifist; 98 | DisplayCustomTextAbovePlayer(0, string.Format("Pacifist enemies: {0}", AreEnemiesPacifist), false, AreEnemiesPacifist ? Color.green : Color.red, 120); 99 | hasInput = true; 100 | } 101 | 102 | if (Input.GetKeyDown(KeyCode.F4)) 103 | { 104 | AreEnemiesNoAi = !AreEnemiesNoAi; 105 | DisplayCustomTextAbovePlayer(0, string.Format("No AI enemies: {0}", AreEnemiesNoAi), false, AreEnemiesNoAi ? Color.green : Color.red, 120); 106 | hasInput = true; 107 | } 108 | 109 | if (Input.GetKeyDown(KeyCode.F5)) 110 | { 111 | ArePlayersInvincible = !ArePlayersInvincible; 112 | DisplayCustomTextAbovePlayer(0, string.Format("Invincible players: {0}", ArePlayersInvincible), false, ArePlayersInvincible ? Color.green : Color.red, 120); 113 | } 114 | 115 | if (hasInput) 116 | { 117 | EnemyEnitity[] enemies = FindObjectsOfType(); 118 | for (int i = 0; i < enemies.Length; i++) 119 | { 120 | UpdateAiSetting(enemies[i]); 121 | } 122 | } 123 | } 124 | 125 | public void Update() 126 | { 127 | FrameLimiterUpdate(); 128 | InputUpdate(); 129 | } 130 | 131 | public void ApplyModifiersOnEnemy(EnemyEnitity enemy) 132 | { 133 | if (AreEnemiesNightmare) 134 | { 135 | enemy._DifficultyType = EnemyEnitity.enumDifficultyType.Nightmare; 136 | } 137 | if (enemy is NPCEntity == false && data.enemiesScalingRatio > 0f && data.enemiesScalingRatio != 1f) 138 | { 139 | float scaleRatio = Mathf.Clamp(data.enemiesScalingRatio, 0.1f, 20f); 140 | Vector3 scale = enemy.transform.localScale; 141 | scale.x *= scaleRatio; 142 | scale.y *= scaleRatio; 143 | enemy.transform.localScale = scale; 144 | } 145 | if (data.activateTrainingMode && enemy._AISetting != null) 146 | { 147 | if (aiSettings.ContainsKey(enemy.ClassType) == false) 148 | { 149 | aiSettings.Add(enemy.ClassType, enemy._AISetting); 150 | } 151 | enemy._AISetting = Instantiate(enemy._AISetting); 152 | 153 | EnemyEnitity[] enemies = FindObjectsOfType(); 154 | for (int i = 0; i < enemies.Length; i++) 155 | { 156 | UpdateAiSetting(enemies[i]); 157 | } 158 | } 159 | } 160 | 161 | public Dictionary aiSettings = new Dictionary(); 162 | 163 | public void UpdateAiSetting(EnemyEnitity enemy) 164 | { 165 | if (data.activateTrainingMode == false || enemy._AISetting == null || enemy is NPCEntity) 166 | return; 167 | 168 | enemy.AI_ChangeState(); 169 | enemy.SetAIChangeState(!AreEnemiesNoAi); 170 | enemy.SetAIChangeState_CombatEntityMainManager(!AreEnemiesNoAi); 171 | 172 | enemy._AISetting.PercentageOfGetupAttack = AreEnemiesPacifist ? 0f : aiSettings[enemy.ClassType].PercentageOfGetupAttack; 173 | enemy._AISetting.AIAttackDetails_RunHeavyAttack.Weight = AreEnemiesPacifist ? 0f : aiSettings[enemy.ClassType].AIAttackDetails_RunHeavyAttack.Weight; 174 | enemy._AISetting.AIAttackDetails_RunSpecialAttack.Weight = AreEnemiesPacifist ? 0f : aiSettings[enemy.ClassType].AIAttackDetails_RunSpecialAttack.Weight; 175 | enemy._AISetting.AIAttackDetails_RunQuickAttack.Weight = AreEnemiesPacifist ? 0f : aiSettings[enemy.ClassType].AIAttackDetails_RunQuickAttack.Weight; 176 | enemy._AISetting.ChasingAttack_WhatToUse = AreEnemiesPacifist ? AI_MoveToAttackRange.AttackType.None : aiSettings[enemy.ClassType].ChasingAttack_WhatToUse; 177 | enemy._AISetting.AttackTime_Min = AreEnemiesPacifist ? float.PositiveInfinity : aiSettings[enemy.ClassType].AttackTime_Min; 178 | enemy._AISetting.AttackTime_Max = AreEnemiesPacifist ? float.PositiveInfinity : aiSettings[enemy.ClassType].AttackTime_Max; 179 | 180 | enemy._AISetting.Retaliate_AftersHPCount_ByBlocking = AreEnemiesPacifist ? false : aiSettings[enemy.ClassType].Retaliate_AftersHPCount_ByBlocking; 181 | enemy._AISetting.Blocking_HowManyBlockHitsBeforeRetaliation = AreEnemiesPacifist ? int.MaxValue : aiSettings[enemy.ClassType].Blocking_HowManyBlockHitsBeforeRetaliation; 182 | enemy._AISetting.Retaliate_AfterManyHitsWithoutIdle_HPCount = AreEnemiesPacifist ? int.MaxValue : aiSettings[enemy.ClassType].Retaliate_AfterManyHitsWithoutIdle_HPCount; 183 | } 184 | 185 | private void LoadModData() 186 | { 187 | if (hasData == false) 188 | { 189 | string modDataJson = File.ReadAllText(".\\ModData.json"); 190 | data = JsonUtility.FromJson(modDataJson); 191 | 192 | hasData = true; 193 | 194 | ApplyFramerateConfig(); 195 | ApplyCustomInputConfig(); 196 | ApplyMonkMode(); 197 | } 198 | } 199 | 200 | private void ApplyFramerateConfig() 201 | { 202 | if (hasData == false) 203 | return; 204 | 205 | //Screen.SetResolution(1920, 640, false); 206 | 207 | if (data.useCustomFramelimiter && HighResolutionTime.IsAvailable) 208 | { 209 | Application.targetFrameRate = -1; 210 | QualitySettings.vSyncCount = 0; 211 | } 212 | else 213 | { 214 | Application.targetFrameRate = data.targetFramerate; 215 | QualitySettings.vSyncCount = data.vSyncCount; 216 | } 217 | } 218 | 219 | private void ApplyCustomInputConfig() 220 | { 221 | if (hasData == false) 222 | return; 223 | 224 | if (data.forceXInput) 225 | { 226 | ReInput.configuration.windowsStandalonePrimaryInputSource = Rewired.Platforms.WindowsStandalonePrimaryInputSource.XInput; 227 | } 228 | 229 | if (_data.interactActionId == "") 230 | _data.interactActionId = "QuickAttack"; 231 | 232 | if (data.swapPhoneNavigationButtons) 233 | { 234 | blockUiInput = "Recruit"; 235 | recruitUiInput = "Block"; 236 | } 237 | 238 | ReplacePlayerMaps(0); 239 | ReplacePlayerMaps(1); 240 | } 241 | 242 | private void ApplyMonkMode() 243 | { 244 | if (data.gameplayModifications.monkMode) 245 | Singleton.instance.UnlockAllMoves = true; 246 | } 247 | 248 | public static string blockUiInput = "Block"; 249 | public static string recruitUiInput = "Recruit"; 250 | 251 | private void ReplacePlayerMaps(int playerId) 252 | { 253 | InputConfig inputConfig = data.GetInputConfig(playerId); 254 | 255 | Rewired.Player.ControllerHelper.MapHelper mapHelper = ReInput.players.GetPlayer(playerId).controllers.maps; 256 | 257 | //Clear previous maps 258 | mapHelper.ClearMaps(ControllerType.Keyboard, false); 259 | 260 | try 261 | { 262 | List args = new List(); 263 | 264 | //Keyboard map 265 | if (playerId == 0) 266 | { 267 | args.AddRange(playerOneElementIdentifierIds); 268 | } 269 | else 270 | { 271 | args.AddRange(playerTwoElementIdentifierIds); 272 | } 273 | 274 | args.AddRange(inputConfig.ToKeycodeArray()); 275 | 276 | args.Add(playerId + 1); 277 | args.Add(playerId + 1); 278 | 279 | string kbMapXml = ""; 280 | kbMapXml = string.Format(kbMapXmlTemplate, args.Select(x => x.ToString()).ToArray()); 281 | mapHelper.AddMapFromXml(ControllerType.Keyboard, 0, kbMapXml); 282 | } 283 | catch (Exception e) 284 | { 285 | File.WriteAllText(string.Format(".\\Player{0}-Remap-ERROR.txt", playerId), string.Concat(e.Message, "\n", e.StackTrace)); 286 | } 287 | } 288 | 289 | private Dictionary actionIdToPromptTypes = new Dictionary() 290 | { 291 | { "Jump", PromptType.A }, 292 | { "QuickAttack", PromptType.X }, 293 | { "HeavyAttack", PromptType.Y }, 294 | { "SpecialAttack", PromptType.B }, 295 | { "Block", PromptType.RTrigger }, 296 | { "Recruit", PromptType.LTrigger }, 297 | { "Start", PromptType.Start }, 298 | }; 299 | 300 | public PromptType ActionIdToPromptType(string actionId) 301 | { 302 | if (actionIdToPromptTypes.ContainsKey(actionId)) 303 | return actionIdToPromptTypes[actionId]; 304 | else 305 | return PromptType.X; 306 | } 307 | 308 | private long lastTime = HighResolutionTime.IsAvailable ? HighResolutionTime.Time : 0L; 309 | 310 | public void FrameLimiterUpdate() 311 | { 312 | if (HighResolutionTime.IsAvailable == false || hasData == false || data.useCustomFramelimiter == false || data.targetFramerate <= 0.0) 313 | return; 314 | 315 | lastTime += TimeSpan.FromSeconds(1.0 / data.targetFramerate).Ticks; 316 | 317 | var now = HighResolutionTime.Time; 318 | 319 | if (now >= lastTime) 320 | { 321 | lastTime = now; 322 | return; 323 | } 324 | else 325 | { 326 | //System.Threading.SpinWait.SpinUntil(() => { return (HighResolutionTime.Time >= lastTime); }); 327 | while (HighResolutionTime.Time < lastTime) { } 328 | } 329 | } 330 | 331 | public int GetHasBeatenGameTimesAnyChar(bool ignoreModData = false) 332 | { 333 | if (ignoreModData == false && data.unlockSecretCharactersOnceForAllSave == false) 334 | return EventManager.instance.GetHasBeatenGameTimes(); 335 | 336 | string hasBeatenGameEventName = EventManager.instance.GetHasBeatenGameEvent().EventName; 337 | int hasBeatenGameCount = 0; 338 | for (int i = 0; i < 4; i++) 339 | { 340 | List eventValues = SaveManager_Main.LoadOrGetDefaultObject(string.Format("Slot_{0}_saveKeySlotFinishedEvents", i), new List(), "savefile"); 341 | for (int j = 0; j < eventValues.Count; j++) 342 | { 343 | if (eventValues[j].EventName == hasBeatenGameEventName) 344 | { 345 | hasBeatenGameCount += eventValues[j].TimesFired; 346 | } 347 | } 348 | } 349 | return hasBeatenGameCount; 350 | } 351 | 352 | public string GetProgressionInfo(Data_FileSelect data_FileSelect) 353 | { 354 | if (data.displayProgressionInfo == false) 355 | return string.Empty; 356 | 357 | StringBuilder sb = new StringBuilder(); 358 | //sb .AppendLine( string.Format("Completion rate: {0}", data_FileSelect.GetTotalCompletionRatio().ToString("p2"))); 359 | int num; 360 | int num2; 361 | string labelYes = Singleton.instance.GetTranslatedKey("LBL_YES", null); 362 | string labelNo = Singleton.instance.GetTranslatedKey("LBL_NO", null); 363 | string label = Singleton.instance.GetTranslatedKey("LBL_COMPLETED_QUEST", null); 364 | sb.AppendLine(string.Format("{0}{1} {2}/{3}", label, data_FileSelect.GetQuestCompletion(out num, out num2).ToString("0%"), num, num2)); 365 | label = Singleton.instance.GetTranslatedKey("LBL_DEFEATED_BOSSES", null); 366 | sb.AppendLine(string.Format("{0}{1} {2}/{3}", label, data_FileSelect.GetBossCompletion(out num, out num2).ToString("0%"), num, num2)); 367 | label = Singleton.instance.GetTranslatedKey("LBL_VISITED_AREAS", null); 368 | sb.AppendLine(string.Format("{0}{1} {2}/{3}", label, data_FileSelect.GetMapCompletion(out num, out num2).ToString("0%"), num, num2)); 369 | label = Singleton.instance.GetTranslatedKey("LBL_SEEN_ITEMS", null); 370 | sb.AppendLine(string.Format("{0}{1} {2}/{3}", label, data_FileSelect.GetUsableCompletion(out num, out num2).ToString("0%"), num, num2)); 371 | label = Singleton.instance.GetTranslatedKey("LBL_UNLOCKED_MOVES", null); 372 | sb.AppendLine(string.Format("{0}{1} {2}/{3}", label, data_FileSelect.GetAttackMoveCompletion(out num, out num2).ToString("0%"), num, num2)); 373 | label = Singleton.instance.GetTranslatedKey("LBL_UNLOCKED_EQUIPS", null); 374 | sb.AppendLine(string.Format("{0}{1} {2}/{3}", label, data_FileSelect.GetEquipCompletion(out num, out num2).ToString("0%"), num, num2)); 375 | label = Singleton.instance.GetTranslatedKey("LBL_BUSTED_STATUES", null); 376 | sb.AppendLine(string.Format("{0}{1} {2}/{3}", label, data_FileSelect.GetStatueCompletion(out num, out num2).ToString("0%"), num, num2)); 377 | label = Singleton.instance.GetTranslatedKey("LBL_RECRUITS_FOUND", null); 378 | sb.AppendLine(string.Format("{0}{1} {2}/{3}", label, data_FileSelect.GetRecruitCompletion(out num, out num2).ToString("0%"), num, num2)); 379 | label = Singleton.instance.GetTranslatedKey("LBL_GAME_BEATEN", null); 380 | sb.AppendLine(string.Format("{0}{1}", label, EventManager.instance.GetHasBeatenGameTimes() > 0 ? labelYes : labelNo)); 381 | label = Singleton.instance.GetTranslatedKey("LBL_NGP_STARTED", null); 382 | sb.AppendLine(string.Format("{0}{1}", label, PersistentData.Instance.IsNewGamePlus ? labelYes : labelNo)); 383 | return sb.ToString(); 384 | } 385 | 386 | public bool IsUsingQuickSkip() 387 | { 388 | if (data.allowQuickSkip == false) 389 | return false; 390 | 391 | HasUsedQuickSkip = true; 392 | 393 | return Input.GetKeyDown(KeyCode.Escape) || Singleton.instance.ButtonDown(GlobalInput.Buttons.Start, GlobalInput.ControllerId.One); 394 | } 395 | 396 | public void DisplayHitDamage(CombatEntity victim, CombatEntity attacker, DamageInfo damageInfo, float dmg, bool hasBombBraEffect) 397 | { 398 | if (!data.displayDamageOnHit || victim.CharacterSprite == null || dmg <= 0f) 399 | return; 400 | 401 | float ratio = 0.5f; 402 | if (attacker != null) 403 | { 404 | RCG.Player player = attacker as RCG.Player; 405 | if (player != null) 406 | { 407 | PlayerCharacters pc = PlayerManager.Instance.Players[player.PlayerID].ClassNameToPlayerCharacter; 408 | if (hasBombBraEffect) 409 | { 410 | ratio = 1f; 411 | } 412 | 413 | int current = damageInfo.DamageAmount; 414 | int min = current; 415 | int max = current; 416 | for (int i = 0; i < player.CharacterDescription.MovesList.Count; i++) 417 | { 418 | int baseDmg = player.CharacterDescription.MovesList[i].DamageInfo.DamageAmount; 419 | 420 | if (baseDmg <= 0) 421 | continue; 422 | 423 | if (min > baseDmg) 424 | { 425 | min = baseDmg; 426 | } 427 | if (max < baseDmg) 428 | { 429 | max = baseDmg; 430 | } 431 | } 432 | ratio = Mathf.InverseLerp((float)min, (float)max, (float)current); 433 | } 434 | else 435 | { 436 | ratio = Mathf.InverseLerp(0.05f, 0.4f, Mathf.CeilToInt(dmg) / (float)(victim.Stamina + Mathf.CeilToInt(dmg))); 437 | } 438 | } 439 | int scaledFontSize = Mathf.RoundToInt(Mathf.Lerp(data.hitDmgTextFontSizeMin, data.hitDmgTextFontSizeMax, ratio)); 440 | DisplayCustomTextAbove(victim, string.Format("{0}{1}", Mathf.CeilToInt(dmg), hasBombBraEffect ? "!" : ""), true, data.hitDmgTextColor, scaledFontSize); 441 | } 442 | 443 | private GameObject _dmgTextPrefab; 444 | 445 | public void DisplayCustomTextAbove(CombatEntity entity, string text, 446 | bool bUseUnscaleTime = true, Color customColor = default(Color), int fontSize = -1) 447 | { 448 | if (_dmgTextPrefab == null) 449 | { 450 | _dmgTextPrefab = Resources.Load("Prefab/DamageText"); 451 | } 452 | 453 | entity.FindCharacterSprite(); 454 | if (entity.CharacterSprite != null) 455 | { 456 | Vector2 rendererPosition = entity.CharacterSprite.RendererPosition; 457 | rendererPosition.y += 1.8f; 458 | GameObject gameObject = UnityEngine.Object.Instantiate(_dmgTextPrefab, rendererPosition, Quaternion.identity); 459 | DamageText damageText = gameObject.GetComponent(); 460 | damageText.SetText(text); 461 | if (customColor.a > 0f) 462 | { 463 | damageText.SetColor(customColor); 464 | } 465 | if (fontSize >= 0) 466 | { 467 | damageText.SetSize(fontSize); 468 | } 469 | if (!bUseUnscaleTime) 470 | { 471 | gameObject.GetComponentInChildren()._useUnscaleTime = false; 472 | return; 473 | } 474 | } 475 | else 476 | { 477 | Debug.LogError("Missing CharacterSprite"); 478 | } 479 | } 480 | 481 | public void DisplayCustomTextAbovePlayer(int playerId, string text, 482 | bool bUseUnscaleTime = true, Color customColor = default(Color), int fontSize = -1) 483 | { 484 | if (playerId < 0 || playerId >= 2) 485 | return; 486 | 487 | CombatEntity playerCmbEntity = PlayerManager.Instance.Players[playerId] as CombatEntity; 488 | if (playerCmbEntity == null) 489 | return; 490 | 491 | DisplayCustomTextAbove(playerCmbEntity, text, bUseUnscaleTime, customColor, fontSize); 492 | } 493 | 494 | public void ModifyNgpDefaultOption(UI_Confirm ngpConfirm) 495 | { 496 | if (data.loiterSelectedByDefault) 497 | ngpConfirm.StartOption = ngpConfirm._noOption; 498 | } 499 | 500 | public string AddStoreInfosToUsableItem(Data_Item item, UI_StoreItemDisplayV2 itemUi, bool isHidden) 501 | { 502 | if (!data.moreInfosInUseableShops || isHidden) 503 | return string.Empty; 504 | 505 | Data_InventoryItem invItem = item as Data_InventoryItem; 506 | 507 | if (invItem == null || invItem.ItemType != InventoryItemTypes.Useable) 508 | return string.Empty; 509 | 510 | int playerId = UI_StoreScreenV2.Instance.CurrentShopper; 511 | PlayerCharacters playerChar = PlayerManager.Instance.Players[playerId].PlayerCharacter; 512 | bool haveConsumed = PlayerGlobalInventory.instance.HaveYouConsumedThis(item, playerChar); 513 | 514 | itemUi.ToggleAlreadyPurchased(haveConsumed, true); 515 | 516 | if (invItem.ItemName == "ITEM_NAME_NOIZE_FIGURE") 517 | return " +1 ALL"; 518 | 519 | return string.Format("{0}{1}{2}{3}{4}{5}", 520 | invItem.Stat_ST > 0 ? string.Format(" +{0} ST", invItem.Stat_ST) : string.Empty, 521 | invItem.Stat_SP > 0 ? string.Format(" +{0} SP", invItem.Stat_SP) : string.Empty, 522 | invItem.Stat_AG > 0 ? string.Format(" +{0} AG", invItem.Stat_AG) : string.Empty, 523 | invItem.Stat_WP > 0 ? string.Format(" +{0} WP", invItem.Stat_WP) : string.Empty, 524 | invItem.Stat_AT > 0 ? string.Format(" +{0} AT", invItem.Stat_AT) : string.Empty, 525 | invItem.Stat_LK > 0 ? string.Format(" +{0} LK", invItem.Stat_LK) : string.Empty 526 | ); 527 | 528 | } 529 | 530 | public UI_InputSelectable GetStartingCharacterSelectable(UI_InputSelectable startSelectable, List selectables, ref int[] hoveredStateByPlayers) 531 | { 532 | if (string.IsNullOrEmpty(data.characterByDefault)) 533 | return startSelectable; 534 | 535 | //Enum.TryParse(); //No Enum.TryParse in C# 4 ??? 536 | PlayerCharacters playerCharacter; 537 | try 538 | { 539 | playerCharacter = (PlayerCharacters)Enum.Parse(typeof(PlayerCharacters), data.characterByDefault, true); 540 | if ((playerCharacter == PlayerCharacters.Riki || playerCharacter == PlayerCharacters.Kunio) && GetHasBeatenGameTimesAnyChar() < 1) 541 | return startSelectable; 542 | } 543 | catch 544 | { 545 | return startSelectable; 546 | } 547 | 548 | for (int i = 0; i < selectables.Count; i++) 549 | { 550 | UI_CharacterSelectOption option = selectables[i].transform.parent.GetComponent(); 551 | if (option.Character == playerCharacter) 552 | { 553 | for (int j = 0; j < hoveredStateByPlayers.Length; j++) 554 | { 555 | hoveredStateByPlayers[j] = i; 556 | } 557 | return selectables[i]; 558 | } 559 | } 560 | 561 | return startSelectable; 562 | } 563 | 564 | public UI_MovesItemDisplay dojoMovesItemPrefab; 565 | 566 | private UI_MovesItemDisplay dojoMoveDisplay; 567 | 568 | public void DisplayDojoMovesItem(Data_MovesItem move, Transform moveDisplayTransform) 569 | { 570 | if (data.displayMoveInputsInDojo == false) 571 | return; 572 | 573 | if (dojoMoveDisplay != null) 574 | Destroy(dojoMoveDisplay.gameObject); 575 | 576 | dojoMoveDisplay = Instantiate(dojoMovesItemPrefab, moveDisplayTransform, false); 577 | dojoMoveDisplay.GenerateInputDisplay(move.DisplayInfo.MoveIcons, move.Inputs); 578 | dojoMoveDisplay.Init(string.Empty, move.MoveType, move.GetBackgroundSprite(move.MoveType), move.DisplayInfo.MaterialSelected, move.DisplayInfo.MaterialUnselected, null); 579 | dojoMoveDisplay.SetDisplayMaterial(true); 580 | dojoMoveDisplay.ToggleUnlockGraphic(false); 581 | 582 | RectTransform dojoMoveRt = GetComponent(); 583 | RectTransform moveDisplayRt = dojoMoveDisplay.GetComponent(); 584 | 585 | moveDisplayRt.anchorMin = new Vector2(0.5f, 0.5f); 586 | moveDisplayRt.anchorMax = new Vector2(0.5f, 0.5f); 587 | moveDisplayRt.anchoredPosition = new Vector2(-112.8f, -404.5f); 588 | } 589 | 590 | public static void AddQuickOverrideToMovesList(System.Collections.Generic.List movesList) 591 | { 592 | foreach (MoveDescription move in movesList) 593 | { 594 | ComboMove lightAttack = null; 595 | foreach (ComboMove combo in move.ComboList) 596 | { 597 | if (combo.Priority == 0) 598 | { 599 | foreach (ComboCondition condition in combo.Conditions) 600 | { 601 | if (condition is InputCondition) 602 | { 603 | InputCondition inputCondition = condition as InputCondition; 604 | if (inputCondition.DirectionModifier == InputDirectionModifier.None && inputCondition.ComboInputs == ComboInputs.Quick) 605 | { 606 | lightAttack = new ComboMove(); 607 | lightAttack.MoveName = combo.MoveName; 608 | lightAttack.ToMove = combo.ToMove; 609 | lightAttack.Priority = 3; 610 | lightAttack.AI_Followup = combo.AI_Followup; 611 | lightAttack.Conditions = new System.Collections.Generic.List(); 612 | foreach (ComboCondition comboCondition in combo.Conditions) 613 | { 614 | if (comboCondition is InputCondition) 615 | { 616 | InputCondition newInputCondition = ScriptableObject.CreateInstance(); 617 | newInputCondition.ComboInputs = ComboInputs.Quick; 618 | newInputCondition.DirectionModifier = InputDirectionModifier.Up; 619 | lightAttack.Conditions.Add(newInputCondition); 620 | } 621 | else 622 | { 623 | lightAttack.Conditions.Add(comboCondition); 624 | } 625 | } 626 | } 627 | } 628 | } 629 | } 630 | } 631 | if (lightAttack != null) 632 | move.ComboList.Add(lightAttack); 633 | } 634 | } 635 | 636 | public static void AddBackAttackComboToMovesList(System.Collections.Generic.List movesList) 637 | { 638 | MoveDescription backAttack = null; 639 | foreach (MoveDescription move in movesList) 640 | { 641 | if (move.MoveName.ToLower().Contains("block") && move.ComboList.Count > 0) 642 | { 643 | backAttack = move.ComboList[0].ToMove; 644 | } 645 | } 646 | 647 | if (backAttack != null) 648 | { 649 | foreach (MoveDescription move in movesList) 650 | { 651 | ComboMove backCombo = null; 652 | foreach (ComboMove combo in move.ComboList) 653 | { 654 | foreach (ComboCondition condition in combo.Conditions) 655 | { 656 | if (condition is InputCondition) 657 | { 658 | InputCondition inputCondition = condition as InputCondition; 659 | if (inputCondition.DirectionModifier == InputDirectionModifier.None && inputCondition.ComboInputs == ComboInputs.Heavy) 660 | { 661 | backCombo = new ComboMove(); 662 | backCombo.MoveName = backAttack.MoveName; 663 | backCombo.ToMove = backAttack; 664 | backCombo.Priority = combo.Priority + 1; 665 | backCombo.AI_Followup = combo.AI_Followup; 666 | backCombo.Conditions = new System.Collections.Generic.List(); 667 | foreach (ComboCondition comboCondition in combo.Conditions) 668 | { 669 | if (comboCondition is InputCondition) 670 | { 671 | InputCondition newInputCondition = ScriptableObject.CreateInstance(); 672 | newInputCondition.ComboInputs = ComboInputs.Heavy; 673 | newInputCondition.DirectionModifier = InputDirectionModifier.Back; 674 | backCombo.Conditions.Add(newInputCondition); 675 | } 676 | else 677 | { 678 | backCombo.Conditions.Add(comboCondition); 679 | } 680 | } 681 | } 682 | } 683 | } 684 | } 685 | if (backCombo != null) 686 | move.ComboList.Add(backCombo); 687 | } 688 | } 689 | } 690 | 691 | public static bool TryBackAttackAutoParry(RCG.Player player, CombatEntity combatEntity) 692 | { 693 | if (player != null) 694 | { 695 | string playerMoveName = player.CurrentMove.MoveName; 696 | if (playerMoveName == "MisakoBruceBackhand" || 697 | playerMoveName == "KyokoDonkeyKick" || 698 | playerMoveName == "RikiCombingHair" || 699 | playerMoveName == "KunioBackElbow") 700 | { 701 | if ((combatEntity.transform.position.x > player.transform.position.x) != (player.Facing.FacingSign > 0)) 702 | { 703 | combatEntity.Fsm.ChangeState(100); 704 | player.Facing.SetFacingFromSign(player.Facing.FacingSign * -1); 705 | player.PlayVFX_Parry(); 706 | player.ChangeState(100); 707 | } 708 | return true; 709 | } 710 | } 711 | return false; 712 | } 713 | 714 | public static void TryHeavyAttackGuardBreak(RCG.Player player, CombatEntity combatEntity) 715 | { 716 | if (player != null && !(combatEntity is BossBaseEntity)) 717 | { 718 | string playerMoveName = player.CurrentMove.MoveName; 719 | if (playerMoveName == "MisakoHaymaker" || 720 | playerMoveName == "KyokoDab" || 721 | playerMoveName == "RikiOneInchPunch" || 722 | playerMoveName == "KunioEat") 723 | { 724 | combatEntity.Fsm.ChangeState(100); 725 | player.ChangeState(100); 726 | } 727 | } 728 | } 729 | 730 | public void AddModLocDataToLocDatabase(Data_LocalizationDatabase locDatabase) 731 | { 732 | List locDataKeys = locDatabase.Keys; 733 | for (int i = 0; i < data.localizationKeys.Length; i++) 734 | { 735 | bool alreadyExist = false; 736 | for (int j = 0; j < locDataKeys.Count; j++) 737 | { 738 | if(data.localizationKeys[i].Key == locDataKeys[j].Key) 739 | { 740 | alreadyExist = true; 741 | locDataKeys[j] = data.localizationKeys[i]; 742 | break; 743 | } 744 | } 745 | 746 | if(alreadyExist == false) 747 | locDatabase.Keys.Add(data.localizationKeys[i]); 748 | } 749 | } 750 | } 751 | } -------------------------------------------------------------------------------- /Code/ModifiedScripts.diff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoArtis/RCGMod/45f1f884d59bc1d8e2e6f7a7a24444efa8900a87/Code/ModifiedScripts.diff -------------------------------------------------------------------------------- /Data/ActionId.csv: -------------------------------------------------------------------------------- 1 | 0,MoveHorizontal 2 | 1,MoveVertical 3 | 2,Jump 4 | 3,QuickAttack 5 | 4,HeavyAttack 6 | 5,SpecialAttack 7 | 6,Block 8 | 7,Recruit 9 | 8,GripChange 10 | 9,Start 11 | 10,Taunt 12 | 11,FaceAccept 13 | 12,FaceBack 14 | -------------------------------------------------------------------------------- /Data/KeyboardKeycode.csv: -------------------------------------------------------------------------------- 1 | 0,None 2 | 8,Backspace 3 | 9,Tab 4 | 12,Clear 5 | 13,Return 6 | 19,Pause 7 | 27,Escape 8 | 32,Space 9 | 33,Exclaim 10 | 34,DoubleQuote 11 | 35,Hash 12 | 36,Dollar 13 | 38,Ampersand 14 | 39,Quote 15 | 40,LeftParen 16 | 41,RightParen 17 | 42,Asterisk 18 | 43,Plus 19 | 44,Comma 20 | 45,Minus 21 | 46,Period 22 | 47,Slash 23 | 48,Alpha0 24 | 49,Alpha1 25 | 50,Alpha2 26 | 51,Alpha3 27 | 52,Alpha4 28 | 53,Alpha5 29 | 54,Alpha6 30 | 55,Alpha7 31 | 56,Alpha8 32 | 57,Alpha9 33 | 58,Colon 34 | 59,Semicolon 35 | 60,Less 36 | 61,Equals 37 | 62,Greater 38 | 63,Question 39 | 64,At 40 | 91,LeftBracket 41 | 92,Backslash 42 | 93,RightBracket 43 | 94,Caret 44 | 95,Underscore 45 | 96,BackQuote 46 | 97,A 47 | 98,B 48 | 99,C 49 | 100,D 50 | 101,E 51 | 102,F 52 | 103,G 53 | 104,H 54 | 105,I 55 | 106,J 56 | 107,K 57 | 108,L 58 | 109,M 59 | 110,N 60 | 111,O 61 | 112,P 62 | 113,Q 63 | 114,R 64 | 115,S 65 | 116,T 66 | 117,U 67 | 118,V 68 | 119,W 69 | 120,X 70 | 121,Y 71 | 122,Z 72 | 127,Delete 73 | 256,Keypad0 74 | 257,Keypad1 75 | 258,Keypad2 76 | 259,Keypad3 77 | 260,Keypad4 78 | 261,Keypad5 79 | 262,Keypad6 80 | 263,Keypad7 81 | 264,Keypad8 82 | 265,Keypad9 83 | 266,KeypadPeriod 84 | 267,KeypadDivide 85 | 268,KeypadMultiply 86 | 269,KeypadMinus 87 | 270,KeypadPlus 88 | 271,KeypadEnter 89 | 272,KeypadEquals 90 | 273,UpArrow 91 | 274,DownArrow 92 | 275,RightArrow 93 | 276,LeftArrow 94 | 277,Insert 95 | 278,Home 96 | 279,End 97 | 280,PageUp 98 | 281,PageDown 99 | 282,F1 100 | 283,F2 101 | 284,F3 102 | 285,F4 103 | 286,F5 104 | 287,F6 105 | 288,F7 106 | 289,F8 107 | 290,F9 108 | 291,F10 109 | 292,F11 110 | 293,F12 111 | 294,F13 112 | 295,F14 113 | 296,F15 114 | 300,Numlock 115 | 301,CapsLock 116 | 302,ScrollLock 117 | 303,RightShift 118 | 304,LeftShift 119 | 305,RightControl 120 | 306,LeftControl 121 | 307,RightAlt 122 | 308,LeftAlt 123 | 309,RightCommand 124 | 310,LeftCommand 125 | 311,LeftWindows 126 | 312,RightWindows 127 | 313,AltGr 128 | 315,Help 129 | 316,Print 130 | 317,SysReq 131 | 318,Break 132 | 319,Menu 133 | -------------------------------------------------------------------------------- /Data/KeyboardMap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {26} 4 | {27} 5 | 0 6 | 7 | 00000000-0000-0000-0000-000000000000 8 | true 9 | 10 | 11 | 0 12 | 1 13 | 1 14 | {0} 15 | 0 16 | false 17 | 0 18 | {13} 19 | 0 20 | 0 21 | 0 22 | true 23 | 24 | 25 | 0 26 | 1 27 | 1 28 | {1} 29 | 0 30 | false 31 | 1 32 | {14} 33 | 0 34 | 0 35 | 0 36 | true 37 | 38 | 39 | 0 40 | 0 41 | 1 42 | {2} 43 | 0 44 | false 45 | 1 46 | {15} 47 | 0 48 | 0 49 | 0 50 | true 51 | 52 | 53 | 0 54 | 0 55 | 1 56 | {3} 57 | 0 58 | false 59 | 0 60 | {16} 61 | 0 62 | 0 63 | 0 64 | true 65 | 66 | 67 | 0 68 | 9 69 | 1 70 | {4} 71 | 0 72 | false 73 | 0 74 | {17} 75 | 0 76 | 0 77 | 0 78 | true 79 | 80 | 81 | 0 82 | 3 83 | 1 84 | {5} 85 | 0 86 | false 87 | 0 88 | {18} 89 | 0 90 | 0 91 | 0 92 | true 93 | 94 | 95 | 0 96 | 4 97 | 1 98 | {6} 99 | 0 100 | false 101 | 0 102 | {19} 103 | 0 104 | 0 105 | 0 106 | true 107 | 108 | 109 | 0 110 | 2 111 | 1 112 | {7} 113 | 0 114 | false 115 | 0 116 | {20} 117 | 0 118 | 0 119 | 0 120 | true 121 | 122 | 123 | 0 124 | 5 125 | 1 126 | {8} 127 | 0 128 | false 129 | 0 130 | {21} 131 | 0 132 | 0 133 | 0 134 | true 135 | 136 | 137 | 0 138 | 6 139 | 1 140 | {9} 141 | 0 142 | false 143 | 0 144 | {22} 145 | 0 146 | 0 147 | 0 148 | true 149 | 150 | 151 | 0 152 | 7 153 | 1 154 | {10} 155 | 0 156 | false 157 | 0 158 | {23} 159 | 0 160 | 0 161 | 0 162 | true 163 | 164 | 165 | 0 166 | 11 167 | 1 168 | {11} 169 | 0 170 | false 171 | 0 172 | {24} 173 | 0 174 | 0 175 | 0 176 | true 177 | 178 | 179 | 0 180 | 12 181 | 1 182 | {12} 183 | 0 184 | false 185 | 0 186 | {25} 187 | 0 188 | 0 189 | 0 190 | true 191 | 192 | 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # River City Girls UserXpMod 2 | 3 | ## ** DOWNLOAD ** 4 | 5 | ### https://github.com/MoArtis/RCGMod/releases 6 | 7 | ## ** What is it ** 8 | 9 | UserXpMod is a "quality of life" mod for the beat 'em-up game **River City Girls** by **Wayforward**. 10 | It allows to tweak multiple small things to fix some issues and improve the user experience on PC. 11 | 12 | ## ** What is new ** 13 | 14 | - Compatible with the official patch 1.1 15 | - Keep the currently held weapon when entering a boss room. (Off by default) 16 | - Make the interaction with doors and bus stops instantaneous. (Off by default) 17 | - Add the two missing moves' input display in the dojo. 18 | - Add two new features to the training mode (No AI on enemies and players invincibility). 19 | - Use the JSON localization file to write your own translation. 20 | - Translated progression details (Only in Spanish and French for now - You can add you own in the mod's data file). 21 | - Various bug fixes on existing features. 22 | 23 | 24 | ## ** What is it used for ** 25 | 26 | [Menus] 27 | - Skip any splash screen, video, dialog with just pressing Start or Escape once. 28 | - Start the game from the main menu. (Off by default) 29 | - Display progression details in the save selection menu. (Off by default) 30 | - Skip the tutorial in New Game Plus. 31 | - Skip the "Press Start" menu. 32 | - "Loiter" option highlighted by default after selecting a file. 33 | - "Next" option highlighted by default in the file settings menu. 34 | - Preselect a favorite character in the character selection screen. (Off by default) 35 | - Useables' consumption status displayed for each character. 36 | - Useables' stat gain displayed in shops. 37 | - Display moves' inputs in Dojos. 38 | - [NEW] Use the JSON localization file to write your own translation. 39 | 40 | [Settings] 41 | - Remap the "interact" action used to pick object on the floor or use doors. 42 | - Remapping the “interact” action will change the button prompt accordingly. 43 | - [NEW] Make the interaction with doors and bus stops instantaneous. (Off by default) 44 | - Remap the keyboard from the mod’s config file. 45 | - Force the game to use XInput to potentially fix the controller related bugs. (Off by default) 46 | - Display the PS4 controller’s button prompts instead of the Xbox ones. (Off by default) 47 | - Configure the vSync settings. 48 | - Use Unity’s frame limiter ("TargetFramerate") or a custom one. (Off by default) 49 | - Swap the phone navigation buttons / keys. (Off by default) 50 | 51 | [Game mechanics/Feedbacks] 52 | - Unlock the secret characters on every save once they are unlocked on at least one save. 53 | - Display the amount of damage on hit like other River City games. 54 | - Customize the color and the size of the Damage and XP gain feedbacks. 55 | - Make Recruits stay and fight for you when summoned. (Off by default) - Code by Muken 56 | - Share rewards between players. (Off by default) - Code by Muken 57 | - Customize the Attack and Combo system. (Off by default) - Code by Muken 58 | - [NEW] Keep the currently held weapon when entering a boss room. (Off by default) 59 | 60 | [Bug fixes] 61 | - [DEPRECATED]Fix a bug that prevented the Book items to work properly when using a character other than Misako. 62 | - Fix a bug that prevented the phone interface to display a maxed-out Stamina stat. 63 | 64 | [Combo Training Mode] 65 | - Press F1 to make enemies invincible. 66 | - Press F2 to force common enemies to spawn as their Nightmare variations. 67 | - Press F3 to prevent common enemies from attacking the players. 68 | - [NEW] Press F4 to deactivate enemies' AI. 69 | - [NEW] Press F5 to make the players invincible. 70 | 71 | [FUN] 72 | - Change the size of the enemies. (Off by default) 73 | 74 | ![vlcsnap-2019-09-16-13h12m18s096](https://user-images.githubusercontent.com/3904610/64936528-0b1bc800-d889-11e9-8c78-314270f8122d.png) 75 | ![1049320_20190922161231_1](https://user-images.githubusercontent.com/3904610/65384687-cb0c8780-dd57-11e9-8c10-23d4860a3fc3.png) 76 | ![1049320_20190922161454_1](https://user-images.githubusercontent.com/3904610/65384688-cba51e00-dd57-11e9-904c-8a458beda79e.png) 77 | ![1049320_20190922163910_1](https://user-images.githubusercontent.com/3904610/65384689-cc3db480-dd57-11e9-998c-53b75cdf594c.png) 78 | 79 | 80 | ## ** How to install ** 81 | 82 | Extract the entire content of the archive where River City Girls is installed. 83 | The install folder on Steam is usually "Steam\steamapps\common\River City Girls". 84 | 85 | It should replace the "Assembly-CSharp.dll" file located in "River City Girls\RiverCityGirls_Data\Managed". 86 | 87 | 88 | ## ** How to use ** 89 | 90 | Next to the "RiverCityGirls.exe" file, you should have a "ModData.json" file. 91 | Open it with any text editor. 92 | 93 | Following the order of the file, this is what you can modify: 94 | 95 | - "allowQuickSkip": Can be set to true or false. if true, allows to skip any video, dialog and the whole intro by pressing Start or Escape once. 96 | 97 | - "startFromMainMenu": Can be set to true or false. If true, always start the game from the main menu. 98 | 99 | - "skipStartMenu": Can be set to true or false. If true, always skip the "Press Start" menu (A.K.A. Title screen). 100 | 101 | - "interactActionId": Can be set to any "action id" like "block" or "recruit" (full list just below). It will replace the button use for interacting with doors or grab objects on the floor. The game is using the "QuickAttack" by default which can be annoying when fighting next to a door or a weapon. 102 | 103 | - "instantInteraction": Can be set to true or false. If true, it will make interaction with doors and bus stop instantaneous. Like in the version 1.0 of the game. 104 | 105 | - "playerOneInputConfigs" & "playerTwoInputConfigs": Each line can be set to a keyboard keycode (full list just below). Remap each action to a new keyboard key. 106 | 107 | - "forceXInput": Can be set to true or false. If true, it might fix some controller related bugs like controlling the two players with only one controller or the controller's inputs being not detected at all. 108 | 109 | - "usePS4buttonPrompts": Can be set to true or false. If true, it will replace the Xbox controller’s input prompts by the PS4 controller’s ones. 110 | 111 | - "displayProgressionInfo": Can be set to true or false. If true, it will display a detailed breakdown of the currently selected save in the save selection menu. 112 | 113 | - "swapPhoneNavigationButtons": Can be set to true or false. If true, it will swap the "Recruit" and "Block" buttons (Left and right on the phones) for the UI / menus. 114 | 115 | - "vSyncCount": Can be set to 0, 1, 2, 3 or 4. 0 will deactivate the vertical sync and decouple the game’s framerate to the monitor refresh rate. Doing so will usually cause “screen tearing” but will allow to use frame limiters (see below). 1, the game default, will sync the framerate with the monitor’s refresh rate. Using 144hz monitor will make the game target a framerate of 144. As the number is the ratio between the monitor refresh rate and the framerate, 2 will make the game target a framerate of 72 (for the same 144hz monitor). Most user needs to keep that value to 1. 116 | 117 | - "targetFramerate": Can be set to -1 or any positive integer. This setting is ignored if the vSync is activated (vSync sets 1,2,3 or 4). If set to -1, the game will run as fast as possible (This will kill your battery very fast). Anything above 0 will use Unity’s built-in frame limiter to limit the framerate to the indicated value. Expect screen tearing when the camera is moving around in game. 118 | 119 | - "useCustomFramelimiter": Can be set to true or false. If true, the game will use a custom Frame limiter to do the same thing as described just before in the "targetFramerate" section. It might give better results than the built-in one. Don’t forget to set the targetFramerate before using this. 120 | 121 | - "displayDamageOnHit": Can be set to true or false. If true, the amount of damage will be displayed when the enemies or the player characters are hit. An exclamation mark will be added to the damage indicator when the “Bomb Bra” effect (One hit KO) is being applied. 122 | 123 | - "xpGetTextColor": Can be set to any real number between 0 and 1 for the Red, blue, green and alpha channels of the desired color. It corresponds to the desired color of the xp gain feedback displayed when an enemy is defeated. The alpha value (“a”) is ignored but setting it to 0 will pick the game’s default feedback text color. 124 | 125 | - "xpGetTextFontSize": Can be set to any positive integer. It defines the font size of the xp gain feedback. The game’s default is 80. Setting it to 0 or less will pick the default value. 126 | 127 | - "hitDmgTextColor": Can be set to any real number between 0 and 1 for the Red, blue, green and alpha channels of the desired color. The same usage as "xpGetTextColor" but for the Hit damage feedback. 128 | 129 | - "hitDmgTextFontSizeMin": Can be set to any positive integer. As the size of the Hit damage feedback is actually dynamic, a minimum and a maximum size can be set. For the players, the size depends on the amount of base damage compared to all the other move of the player’s character. For the enemies, the size is determined from the portion of health lost from their attacks. 130 | 131 | - "hitDmgTextFontSizeMax": Can be set to any positive integer. See "hitDmgTextFontSizeMin". 132 | 133 | - "moreInfosInUseableShops": Can be set to true or false. If true, the shop selling foods or other useable will make it clear if a character already used an item. It will also display the items' stat gains. 134 | 135 | - "loiterSelectedByDefault": Can be set to true or false. If true, the "loiter" option will be selected by default after selecting a file. Making it unlikely to start a new game plus by mistake and reset the story progress. 136 | 137 | - "startSelectedByDefault": Can be set to true or false. If true, the "Next" option in the selected by default in the file settings menu. 138 | 139 | - "noTutorialInNewGamePlus": Can be set to true or false. If true, remove the tutorial in New Game Plus. 140 | 141 | - "characterByDefault": Can be set to Misako, Kyoko, Kunio, Riki or left empty (""). If set to a character name, the character selection screen will try to select that character by default for the two players. If value is set to locked character, left empty or invalid, the default behavior will be used. 142 | 143 | - "unlockSecretCharactersOnceForAllSave": Can be set to true or false. If true, unlocking the secret playable characters only need to be done once in order to make them available for any save. 144 | 145 | - "fixBooksBug": Can be set to true or false. If true, fix a bug that prevented the Book items to work properly when using a character other than Misako. 146 | 147 | - "fixMaxStaminaBug": Can be set to true or false. If true, fix a bug that prevented the phone interface to display a maxed-out Stamina stat. 148 | 149 | - "activateTrainingMode": Can be set to true or false. If true, allows to use the F1 to F5 keys to switch on and off different features related to "combo training". 150 | 151 | - "enemiesScalingRatio": Can be set to any real number between 0.1 and 20. Will change the size of the enemies. :D 152 | 153 | - "gameplayModifications": This section contains flags that change aspects of gameplay 154 | 155 | - "activeRecruits": Can be set to true or false. If true, change the recruit system entirely. Recruits will now actively participate to the fight until they are taken down by the enemies or until the player left the current room. Summoning a recruit costs a heart, as well as letting a recruit get KO'ed. Press the recruit button again to dismiss before they are KOed. 156 | 157 | - "quickComboOverride": Can be set to true or false. If true, after purchasing extensions to the quick combo, you can still do the finisher early by holding up while pressing quick attack. 158 | 159 | - "backAttackCombo": Can be set to true or false. If true, in all combos where you can combo into a heavy attack, you can also combo into a back attack by holding back while pressing heavy attack. 160 | 161 | - "backAttackAutoParry": Can be set to true or false. If true, all back attacks have an auto-parry property against all non-boss attacks coming from the back. 162 | 163 | - "heavyAttackGuardBreak": Can be set to true or false. If true, forward + heavy attacks will break the guard of non-boss enemies. 164 | 165 | - "sharedRewards": Can be set to true or false. If true, all XP and money from fighting enemies will be shared between players in coop mode. 166 | 167 | - "monkMode": Can be set to true or false. This is a challenge mode where you play in poverty. No money will be earned (though you will start with all techniques unlocked, and burgers are free during the Godai burger quest). Enemies will have a 20% health bonus, act 1 and 2 enemies will be slightly tougher, all bosses will have double health, and Sabuko will have a nice end-boss surprise for you. In return, all characters will start with both Kyoko's and Misako's starting equipment. You may want to put on those gym shorts, since you don't have a lot of other ways to get health back :) 168 | 169 | - "alwaysKeepWeapons": Can be set to true or false. If true, the player characters will keep their currently held weapon when entering a boss room. 170 | 171 | - "useJsonLocalizationData": Can be set to true or false. If true, the game will use the localization file located in "RiverCityGirls_Data\StreamingAssets\LocalizationData" instead of its internal data. 172 | 173 | - "localizationKeys": A list of localization keys only used by the mod. Can also be used to replace any existing key used by the game. 174 | 175 | 176 | ## ** How to uninstall ** 177 | 178 | If you want or need to restore the original files, you should be able to do that on Steam by using the "Verify integrity of the game files" option. 179 | 180 | In your steam's library, Right click on the game name, properties, local files. The option should be here. 181 | 182 | 183 | ## ** Available Action Id ** 184 | 185 | ``` 186 | Jump 187 | QuickAttack 188 | HeavyAttack 189 | SpecialAttack 190 | Block 191 | Recruit 192 | Start 193 | ``` 194 | 195 | 196 | ## ** Available keyboard keycode ** 197 | 198 | ``` 199 | Backspace 200 | Tab 201 | Clear 202 | Return 203 | Pause 204 | Escape 205 | Space 206 | Exclaim 207 | DoubleQuote 208 | Hash 209 | Dollar 210 | Ampersand 211 | Quote 212 | LeftParen 213 | RightParen 214 | Asterisk 215 | Plus 216 | Comma 217 | Minus 218 | Period 219 | Slash 220 | Alpha0 221 | Alpha1 222 | Alpha2 223 | Alpha3 224 | Alpha4 225 | Alpha5 226 | Alpha6 227 | Alpha7 228 | Alpha8 229 | Alpha9 230 | Colon 231 | Semicolon 232 | Less 233 | Equals 234 | Greater 235 | Question 236 | At 237 | LeftBracket 238 | Backslash 239 | RightBracket 240 | Caret 241 | Underscore 242 | BackQuote 243 | A 244 | B 245 | C 246 | D 247 | E 248 | F 249 | G 250 | H 251 | I 252 | J 253 | K 254 | L 255 | M 256 | N 257 | O 258 | P 259 | Q 260 | R 261 | S 262 | T 263 | U 264 | V 265 | W 266 | X 267 | Y 268 | Z 269 | Delete 270 | Keypad0 271 | Keypad1 272 | Keypad2 273 | Keypad3 274 | Keypad4 275 | Keypad5 276 | Keypad6 277 | Keypad7 278 | Keypad8 279 | Keypad9 280 | KeypadPeriod 281 | KeypadDivide 282 | KeypadMultiply 283 | KeypadMinus 284 | KeypadPlus 285 | KeypadEnter 286 | KeypadEquals 287 | UpArrow 288 | DownArrow 289 | RightArrow 290 | LeftArrow 291 | Insert 292 | Home 293 | End 294 | PageUp 295 | PageDown 296 | F1 297 | F2 298 | F3 299 | F4 300 | F5 301 | F6 302 | F7 303 | F8 304 | F9 305 | F10 306 | F11 307 | F12 308 | F13 309 | F14 310 | F15 311 | Numlock 312 | CapsLock 313 | ScrollLock 314 | RightShift 315 | LeftShift 316 | RightControl 317 | LeftControl 318 | RightAlt 319 | LeftAlt 320 | RightCommand 321 | LeftCommand 322 | LeftWindows 323 | RightWindows 324 | AltGr 325 | Help 326 | Print 327 | SysReq 328 | Break 329 | Menu 330 | ``` 331 | --------------------------------------------------------------------------------