44#include " HookSystem.h"
55#include " EntityPlus.h"
66
7+
8+ // Context handling
9+
710struct ItemSpoofCallContext
811{
912 bool isLuaRequest = false ;
@@ -24,16 +27,18 @@ ItemSpoofCallContext GetContextAndReset()
2427 return context;
2528}
2629
27- static std::bitset<CollectibleType::NUM_COLLECTIBLES > s_reworkedCollectibles;
28- static std::bitset<ePlayerType::NUM_PLAYER_TYPES > s_reworkedBirthrights;
29- static std::bitset<TrinketType::NUM_TRINKETS > s_reworkedTrinkets;
30-
31- void ItemSpoofSystem::StartLuaRequest (bool ignoreSpoof)
32- {
30+ void ItemSpoofSystem::StartLuaRequest (bool ignoreSpoof) {
3331 s_ItemSpoofCallContext.isLuaRequest = true ;
3432 s_ItemSpoofCallContext.ignoreSpoof = ignoreSpoof;
3533}
3634
35+
36+ // "Reworked" Items
37+
38+ static std::bitset<CollectibleType::NUM_COLLECTIBLES > s_reworkedCollectibles;
39+ static std::bitset<ePlayerType::NUM_PLAYER_TYPES > s_reworkedBirthrights;
40+ static std::bitset<TrinketType::NUM_TRINKETS > s_reworkedTrinkets;
41+
3742void ItemSpoofSystem::ReworkCollectible (int collectible)
3843{
3944 assert (CollectibleType::COLLECTIBLE_NULL < collectible && collectible < CollectibleType::NUM_COLLECTIBLES );
@@ -82,6 +87,84 @@ static bool is_reworked_trinket(int trinket)
8287 return s_reworkedTrinkets.test (trinket);
8388}
8489
90+
91+ // XML Innate Items
92+
93+ static std::unordered_map<int , std::unordered_map<int , int >> s_playerTypeInnateCollectibles;
94+ static std::unordered_map<int , std::unordered_map<int , int >> s_playerTypeInnateTrinkets;
95+
96+ const std::unordered_map<int , int >* GetPlayerTypeInnateCollectibles (int playerType) {
97+ if (auto it = s_playerTypeInnateCollectibles.find (playerType); it != s_playerTypeInnateCollectibles.end ())
98+ return &it->second ;
99+ return nullptr ;
100+ }
101+
102+ int GetPlayerTypeInnateCollectibleCount (Entity_Player* player, int collectible) {
103+ if (const auto * ptypeCollectibles = GetPlayerTypeInnateCollectibles (player->_playerType )) {
104+ if (auto it = ptypeCollectibles->find (collectible); it != ptypeCollectibles->end ()) {
105+ return it->second ;
106+ }
107+ }
108+ return 0 ;
109+ }
110+
111+ const std::unordered_map<int , int >* GetPlayerTypeInnateTrinkets (int playerType) {
112+ if (auto it = s_playerTypeInnateTrinkets.find (playerType); it != s_playerTypeInnateTrinkets.end ())
113+ return &it->second ;
114+ return nullptr ;
115+ }
116+
117+ int GetPlayerTypeInnateTrinketCount (Entity_Player* player, int collectible) {
118+ if (const auto * ptypeTrinkets = GetPlayerTypeInnateTrinkets (player->_playerType )) {
119+ if (auto it = ptypeTrinkets->find (collectible); it != ptypeTrinkets->end ()) {
120+ return it->second ;
121+ }
122+ }
123+ return 0 ;
124+ }
125+
126+ std::vector<int > ParseCommaSeparatedIdList (const std::string& str) {
127+ std::vector<int > ids;
128+
129+ std::stringstream ss (str);
130+ std::string item;
131+
132+ while (std::getline (ss, item, ' ,' )) {
133+ int id = std::atoi (item.c_str ());
134+ if (id > 0 ) {
135+ ids.push_back (id);
136+ }
137+ }
138+
139+ return ids;
140+ }
141+
142+ HOOK_METHOD_PRIORITY (ModManager, LoadConfigs, -9999 , () -> void ) {
143+ RegisterCustomXMLAttr (XMLStuff.PlayerData , " innateitems" , XMLStuff.ItemData );
144+ RegisterCustomXMLAttr (XMLStuff.PlayerData , " innatetrinkets" , XMLStuff.TrinketData );
145+
146+ super ();
147+
148+ s_playerTypeInnateCollectibles.clear ();
149+ s_playerTypeInnateTrinkets.clear ();
150+
151+ for (const auto & [playerType, playerData] : XMLStuff.PlayerData ->nodes ) {
152+ if (auto it = playerData.find (" innateitems" ); it != playerData.end ()) {
153+ for (const int id : ParseCommaSeparatedIdList (it->second )) {
154+ s_playerTypeInnateCollectibles[playerType][id]++;
155+ }
156+ }
157+ if (auto it = playerData.find (" innatetrinkets" ); it != playerData.end ()) {
158+ for (const int id : ParseCommaSeparatedIdList (it->second )) {
159+ s_playerTypeInnateTrinkets[playerType][id]++;
160+ }
161+ }
162+ }
163+ }
164+
165+
166+ // Core ItemSpoofSystem hooks
167+
85168HOOK_METHOD_PRIORITY (Entity_Player, HasCollectible, -9999 , (int collectible, bool ignoreModifiers) -> bool )
86169{
87170 const auto context = GetContextAndReset ();
@@ -105,6 +188,10 @@ HOOK_METHOD_PRIORITY(Entity_Player, HasCollectible, -9999, (int collectible, boo
105188 return true ;
106189 }
107190 }
191+
192+ if (GetPlayerTypeInnateCollectibleCount (this , collectible) > 0 ) {
193+ return true ;
194+ }
108195 }
109196
110197 return super (collectible, ignoreModifiers);
@@ -132,6 +219,8 @@ HOOK_METHOD_PRIORITY(Entity_Player, GetCollectibleNum, -9999, (int collectible,
132219 }
133220 innateCount += spoofs.GetInnateCount (collectible);
134221 }
222+
223+ innateCount += GetPlayerTypeInnateCollectibleCount (this , collectible);
135224 }
136225
137226 return innateCount + super (collectible, ignoreModifiers);
@@ -160,6 +249,10 @@ HOOK_METHOD_PRIORITY(Entity_Player, HasTrinket, -9999, (unsigned int trinket, bo
160249 return true ;
161250 }
162251 }
252+
253+ if (GetPlayerTypeInnateTrinketCount (this , trinket) > 0 ) {
254+ return true ;
255+ }
163256 }
164257
165258 return super (trinket, ignoreModifiers);
@@ -187,6 +280,8 @@ HOOK_METHOD_PRIORITY(Entity_Player, GetTrinketMultiplier, -9999, (unsigned int t
187280 }
188281 innateMult += spoofs.GetInnateCount (trinket & TRINKET_ID_MASK ) + spoofs.GetInnateCount (trinket | TRINKET_GOLDEN_FLAG ) * 2 ;
189282 }
283+
284+ innateMult += GetPlayerTypeInnateTrinketCount (this , trinket);
190285 }
191286
192287 int vanillaMult = super (trinket);
@@ -228,12 +323,42 @@ HOOK_METHOD_PRIORITY(Entity_Player, UpdateEffects, -9999, () -> void)
228323{
229324 if (EntityPlayerPlus* playerPlus = GetEntityPlayerPlus (this ))
230325 {
326+ playerPlus->itemSpoofs .CheckPlayerType (*this , false );
231327 playerPlus->itemSpoofs .GetCollectibleSpoof ().UpdateTemporaryTimers (*this );
232328 playerPlus->itemSpoofs .GetTrinketSpoof ().UpdateTemporaryTimers (*this );
233329 }
234330 super ();
235331}
236332
333+ // Detect PlayerType changes where they can happen.
334+ HOOK_METHOD_PRIORITY (Entity_Player, ChangePlayerType, 9999 , (int playerType, bool unk) -> void ) {
335+ super (playerType, unk);
336+ if (EntityPlayerPlus* playerPlus = GetEntityPlayerPlus (this )) {
337+ playerPlus->itemSpoofs .CheckPlayerType (*this , false );
338+ }
339+ }
340+ HOOK_METHOD_PRIORITY (Entity_Player, InitPostLevelInitStats, 9999 , () -> void ) {
341+ if (EntityPlayerPlus* playerPlus = GetEntityPlayerPlus (this )) {
342+ playerPlus->itemSpoofs .CheckPlayerType (*this , false );
343+ }
344+ super ();
345+ }
346+ HOOK_METHOD_PRIORITY (Entity_Player, TriggerDeath, 9999 , (bool checkOnly) -> bool ) {
347+ bool res = super (checkOnly);
348+ if (!checkOnly) {
349+ if (EntityPlayerPlus* playerPlus = GetEntityPlayerPlus (this )) {
350+ playerPlus->itemSpoofs .CheckPlayerType (*this , false );
351+ }
352+ }
353+ return res;
354+ }
355+ HOOK_METHOD_PRIORITY (Entity_Player, RestoreGameState, -9999 , (GameStatePlayer* saveState) -> void ) {
356+ super (saveState);
357+ if (EntityPlayerPlus* playerPlus = GetEntityPlayerPlus (this )) {
358+ playerPlus->itemSpoofs .CheckPlayerType (*this , true );
359+ }
360+ }
361+
237362
238363// Serialization & Data Structure Details
239364
@@ -592,4 +717,36 @@ namespace ItemSpoofSystem
592717 }
593718 }
594719
720+ void PlayerItemSpoofs::CheckPlayerType (Entity_Player& player, bool restoringGameState) {
721+ const int prevPlayerType = lastPlayerType_;
722+ const int currentPlayerType = player._playerType ;
723+ if (prevPlayerType != currentPlayerType) {
724+ lastPlayerType_ = currentPlayerType;
725+ if (restoringGameState) {
726+ return ;
727+ }
728+ if (const auto * oldCols = GetPlayerTypeInnateCollectibles (prevPlayerType)) {
729+ for (const auto & [id, count] : *oldCols) {
730+ collectibleSpoof_.TriggerRemoved (player, id, count, false );
731+ }
732+ }
733+ if (const auto * newCols = GetPlayerTypeInnateCollectibles (currentPlayerType)) {
734+ for (const auto & [id, count] : *newCols) {
735+ collectibleSpoof_.TriggerAdded (player, id, count, false , false );
736+ }
737+ }
738+ if (const auto * oldTrinkets = GetPlayerTypeInnateTrinkets (prevPlayerType)) {
739+ for (const auto & [id, count] : *oldTrinkets) {
740+ trinketSpoof_.TriggerRemoved (player, id, count, false );
741+ }
742+ }
743+ if (const auto * newTrinkets = GetPlayerTypeInnateTrinkets (currentPlayerType)) {
744+ for (const auto & [id, count] : *newTrinkets) {
745+ trinketSpoof_.TriggerAdded (player, id, count, false , false );
746+ }
747+ }
748+ player.EvaluateItems ();
749+ }
750+ }
751+
595752} // namespace ItemSpoofSystem
0 commit comments