Skip to content

Commit 805a85a

Browse files
committed
yah this code is messy and doesn't really handle the edge case the way I'd like it to, and yah it completely replaces the base-game code with a prefix - but hey it works and I've spent over 8 hours straight on it now so I'm going to have to say its good enough for the time being
0 parents  commit 805a85a

7 files changed

Lines changed: 336 additions & 0 deletions

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
**/Libs
2+
**/.vs
3+
**/bin
4+
**/obj
5+
**/Library
6+
**/ThunderStorePackage
7+
**/icon

CardChoiceSpawnUniqueCardPatch.dll

9.5 KB
Binary file not shown.

CardChoiceSpawnUniqueCardPatch.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.31321.278
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CardChoiceSpawnUniqueCardPatch", "CardChoiceSpawnUniqueCardPatch\CardChoiceSpawnUniqueCardPatch.csproj", "{06CD9F54-D5C2-4229-B420-6974CA845AB2}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{06CD9F54-D5C2-4229-B420-6974CA845AB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{06CD9F54-D5C2-4229-B420-6974CA845AB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{06CD9F54-D5C2-4229-B420-6974CA845AB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{06CD9F54-D5C2-4229-B420-6974CA845AB2}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {B689B33B-751C-4229-8858-C5FB26387AFE}
24+
EndGlobalSection
25+
EndGlobal
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
using BepInEx; // requires BepInEx.dll and BepInEx.Harmony.dll
2+
using UnityEngine; // requires UnityEngine.dll, UnityEngine.CoreModule.dll, and UnityEngine.AssetBundleModule.dll
3+
using HarmonyLib; // requires 0Harmony.dll
4+
using System.Collections;
5+
using System.Reflection;
6+
using System.Linq;
7+
using System.Collections.Generic;
8+
using System;
9+
using System.Runtime.CompilerServices;
10+
// requires Assembly-CSharp.dll
11+
// requires MMHOOK-Assembly-CSharp.dll
12+
13+
namespace CardChoiceSpawnUniqueCardPatch
14+
{
15+
[BepInPlugin(ModId, ModName, "0.0.0.0")]
16+
[BepInProcess("Rounds.exe")]
17+
public class CardChoiceSpawnUniqueCardPatch : BaseUnityPlugin
18+
{
19+
private void Awake()
20+
{
21+
new Harmony(ModId).PatchAll();
22+
}
23+
private void Start()
24+
{
25+
26+
}
27+
28+
private const string ModId = "pykess.rounds.plugins.cardchoicespawnuniquecardpatch";
29+
30+
private const string ModName = "CardChoiceSpawnUniqueCardPatch";
31+
}
32+
// stolen from PCE
33+
public sealed class Cards
34+
{
35+
// singleton design
36+
public static readonly Cards instance = new Cards();
37+
private Cards()
38+
{
39+
Cards instance = this;
40+
}
41+
public bool CardIsUniqueFromCards(CardInfo card, CardInfo[] cards)
42+
{
43+
bool unique = true;
44+
45+
foreach (CardInfo otherCard in cards)
46+
{
47+
if (card.cardName == otherCard.cardName)
48+
{
49+
unique = false;
50+
}
51+
}
52+
53+
return unique;
54+
}
55+
56+
public bool CardDoesNotConflictWithCards(CardInfo card, CardInfo[] cards)
57+
{
58+
bool conflicts = false;
59+
60+
foreach (CardInfo otherCard in cards)
61+
{
62+
if (card.categories.Intersect(otherCard.blacklistedCategories).Any())
63+
{
64+
conflicts = true;
65+
}
66+
}
67+
68+
return conflicts;
69+
}
70+
71+
public bool PlayerIsAllowedCard(Player player, CardInfo card)
72+
{
73+
bool blacklisted = false;
74+
75+
foreach (CardInfo currentCard in player.data.currentCards)
76+
{
77+
if (card.categories.Intersect(currentCard.blacklistedCategories).Any())
78+
{
79+
blacklisted = true;
80+
}
81+
}
82+
83+
return !blacklisted && (card.allowMultiple || !player.data.currentCards.Where(cardinfo => cardinfo.name == card.name).Any());
84+
85+
}
86+
public CardInfo GetRandomCardWithCondition(CardChoice cardChoice, Player player, Func<CardInfo, Player, bool> condition, int maxattempts = 1000)
87+
{
88+
89+
CardInfo card = ((GameObject)typeof(CardChoice).InvokeMember("GetRanomCard",
90+
BindingFlags.Instance | BindingFlags.InvokeMethod |
91+
BindingFlags.NonPublic, null, cardChoice, new object[] { })).GetComponent<CardInfo>();
92+
93+
int i = 0;
94+
95+
// draw a random card until it's an uncommon or the maximum number of attempts was reached
96+
while (!condition(card, player) && i < maxattempts)
97+
{
98+
card = ((GameObject)typeof(CardChoice).InvokeMember("GetRanomCard",
99+
BindingFlags.Instance | BindingFlags.InvokeMethod |
100+
BindingFlags.NonPublic, null, cardChoice, new object[] { })).GetComponent<CardInfo>();
101+
i++;
102+
}
103+
104+
if (!condition(card, player))
105+
{
106+
return null;
107+
}
108+
else
109+
{
110+
return card;
111+
}
112+
113+
}
114+
115+
public int GetCardID(CardInfo card)
116+
{
117+
return Array.IndexOf(global::CardChoice.instance.cards, card);
118+
}
119+
public CardInfo GetCardWithID(int cardID)
120+
{
121+
return global::CardChoice.instance.cards[cardID];
122+
}
123+
}
124+
125+
[Serializable]
126+
[HarmonyPatch(typeof(CardChoice), "SpawnUniqueCard")]
127+
class CardChoicePatchSpawnUniqueCard
128+
{
129+
private static bool Prefix(ref GameObject __result, CardChoice __instance, Vector3 pos, Quaternion rot)
130+
{
131+
Player player;
132+
if ((PickerType)Traverse.Create(__instance).Field("pickerType").GetValue() == PickerType.Team)
133+
{
134+
player = PlayerManager.instance.GetPlayersInTeam(__instance.pickrID)[0];
135+
}
136+
else
137+
{
138+
player = PlayerManager.instance.players[__instance.pickrID];
139+
}
140+
141+
CardInfo validCard = Cards.instance.GetRandomCardWithCondition(__instance, player, CardChoicePatchSpawnUniqueCard.GetCondition(__instance));
142+
143+
if (validCard != null)
144+
{
145+
GameObject gameObject = (GameObject)typeof(CardChoice).InvokeMember("Spawn",
146+
BindingFlags.Instance | BindingFlags.InvokeMethod |
147+
BindingFlags.NonPublic, null, __instance, new object[] { validCard.gameObject, pos, rot });
148+
gameObject.GetComponent<CardInfo>().sourceCard = validCard.GetComponent<CardInfo>();
149+
gameObject.GetComponentInChildren<DamagableEvent>().GetComponent<Collider2D>().enabled = false;
150+
151+
__result = gameObject;
152+
}
153+
else
154+
{
155+
// there are no valid cards left - this is an extremely unlikely scenario, only achievable if most of the cards have been disabled
156+
157+
// if any valid cards were found, just return one of those, even though its a duplicate
158+
159+
160+
List<GameObject> spawnedCards = (List<GameObject>)Traverse.Create(__instance).Field("spawnedCards").GetValue();
161+
162+
GameObject card;
163+
164+
if (spawnedCards.Count > 0)
165+
{
166+
CardInfo cardInfo = Cards.instance.GetCardWithID(Cards.instance.GetCardID(spawnedCards[0].GetComponent<CardInfo>().sourceCard));
167+
card = cardInfo.gameObject;
168+
}
169+
170+
// if no valid cards could be found, then just get any card at all because that's better than crashing the game
171+
else
172+
{
173+
card = ((GameObject)typeof(CardChoice).InvokeMember("GetRanomCard",
174+
BindingFlags.Instance | BindingFlags.InvokeMethod |
175+
BindingFlags.NonPublic, null, __instance, new object[] { }));
176+
}
177+
GameObject gameObject = (GameObject)typeof(CardChoice).InvokeMember("Spawn",
178+
BindingFlags.Instance | BindingFlags.InvokeMethod |
179+
BindingFlags.NonPublic, null, __instance, new object[] { card.gameObject, pos, rot });
180+
gameObject.GetComponent<CardInfo>().sourceCard = card.GetComponent<CardInfo>();
181+
gameObject.GetComponentInChildren<DamagableEvent>().GetComponent<Collider2D>().enabled = false;
182+
183+
184+
__result = gameObject;
185+
186+
}
187+
188+
return false; // do not run the original method (BAD IDEA)
189+
}
190+
private static Func<CardInfo, Player, bool> GetCondition(CardChoice instance)
191+
{
192+
return (card, player) => (CardChoicePatchSpawnUniqueCard.BaseCondition(instance)(card, player) && CardChoicePatchSpawnUniqueCard.CorrectedCondition(instance)(card, player));
193+
}
194+
private static Func<CardInfo, Player, bool> CorrectedCondition(CardChoice instance)
195+
{
196+
return (card, player) => (Cards.instance.PlayerIsAllowedCard(player, card));
197+
}
198+
private static Func<CardInfo, Player, bool> BaseCondition(CardChoice instance)
199+
{
200+
return (card, player) =>
201+
{
202+
List<GameObject> spawnedCards = (List<GameObject>)Traverse.Create(instance).Field("spawnedCards").GetValue();
203+
for (int i = 0; i < spawnedCards.Count; i++)
204+
{
205+
bool flag = spawnedCards[i].GetComponent<CardInfo>().cardName == card.cardName;
206+
if (instance.pickrID != -1)
207+
{
208+
Holdable holdable = player.data.GetComponent<Holding>().holdable;
209+
if (holdable)
210+
{
211+
Gun component2 = holdable.GetComponent<Gun>();
212+
Gun component3 = card.GetComponent<Gun>();
213+
if (component3 && component2 && component3.lockGunToDefault && component2.lockGunToDefault)
214+
{
215+
flag = true;
216+
}
217+
}
218+
for (int j = 0; j < player.data.currentCards.Count; j++)
219+
{
220+
CardInfo component4 = player.data.currentCards[j].GetComponent<CardInfo>();
221+
for (int k = 0; k < component4.blacklistedCategories.Length; k++)
222+
{
223+
for (int l = 0; l < card.categories.Length; l++)
224+
{
225+
if (card.categories[l] == component4.blacklistedCategories[k])
226+
{
227+
flag = true;
228+
}
229+
}
230+
}
231+
if (!component4.allowMultiple && card.cardName == component4.cardName)
232+
{
233+
flag = true;
234+
}
235+
}
236+
}
237+
if (flag)
238+
{
239+
return false;
240+
}
241+
}
242+
return true;
243+
};
244+
}
245+
}
246+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<Reference Include="0Harmony">
9+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\BepInEx\core\0Harmony.dll</HintPath>
10+
</Reference>
11+
<Reference Include="Assembly-CSharp">
12+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\Rounds_Data\Managed\Assembly-CSharp.dll</HintPath>
13+
</Reference>
14+
<Reference Include="BepInEx">
15+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\BepInEx\core\BepInEx.dll</HintPath>
16+
</Reference>
17+
<Reference Include="BepInEx.Harmony">
18+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\BepInEx\core\BepInEx.Harmony.dll</HintPath>
19+
</Reference>
20+
<Reference Include="Photon3Unity3D">
21+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\Rounds_Data\Managed\Photon3Unity3D.dll</HintPath>
22+
</Reference>
23+
<Reference Include="PhotonUnityNetworking">
24+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\Rounds_Data\Managed\PhotonUnityNetworking.dll</HintPath>
25+
</Reference>
26+
<Reference Include="PhotonUnityNetworking.Utilities">
27+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\Rounds_Data\Managed\PhotonUnityNetworking.Utilities.dll</HintPath>
28+
</Reference>
29+
<Reference Include="UnboundLib">
30+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\BepInEx\plugins\UnboundLib.dll</HintPath>
31+
</Reference>
32+
<Reference Include="UnityEngine">
33+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\Rounds_Data\Managed\UnityEngine.dll</HintPath>
34+
</Reference>
35+
<Reference Include="UnityEngine.CoreModule">
36+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\Rounds_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
37+
</Reference>
38+
<Reference Include="UnityEngine.Physics2DModule">
39+
<HintPath>..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\ROUNDS\Rounds_Data\Managed\UnityEngine.Physics2DModule.dll</HintPath>
40+
</Reference>
41+
</ItemGroup>
42+
43+
</Project>

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# CardChoiceSpawnUniqueCardPatch
2+
------------------
3+
4+
This is a utility mod which patches the erroneous `CardChoice.SpawnUniqueCard` method in the base game code.
5+
6+
Previously, the game would not properly check if the `allowMultiple` or `blacklistedcategories` fields of a card should have prevented it from being offered. Moreover, if it had done this, it would have been possible to crash the game since the method was called recursively with no garunteed exit.

manifest.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "CardChoiceSpawnUniqueCardPatch",
3+
"version_number": "0.0.0",
4+
"website_url": "https://github.com/Rounds-Modding/CardChoiceSpawnUniqueCardPatch",
5+
"description": "Patches erroneous logic in the base game CardChoice method SpawnUniqueCard",
6+
"dependencies": [
7+
"BepInEx-BepInExPack_ROUNDS-5.4.1100"
8+
]
9+
}

0 commit comments

Comments
 (0)