@@ -11,7 +11,6 @@ import {
1111 PlayerVariant ,
1212 SoundEffect ,
1313} from "isaac-typescript-definitions" ;
14- import type { SaveData } from "isaacscript-common" ;
1514import {
1615 anyPlayerIs ,
1716 Callback ,
@@ -47,16 +46,31 @@ import { Debugger } from "../../util/debug";
4746import { setTearColor } from "../../util/effects" ;
4847import { eraseEnemies , getEnemyKey } from "../../util/enemies" ;
4948import { rollWeighted } from "../../util/rng" ;
49+ import type { ModSaveData } from "../../util/save" ;
5050import { SAVE_DATA } from "../../util/save" ;
5151import { Character } from "../Character" ;
5252import { isMiku , PlayerTypeCustom } from "../enum" ;
5353import type { MikuPlayerData } from "./MikuCharacter" ;
54+ import {
55+ isNoteItemDisabled ,
56+ MikuAttackMode as MikuNoteMode ,
57+ setMikuAttackMode ,
58+ } from "./mikuHelper" ;
5459
5560export interface TaintedMikuData extends MikuPlayerData {
56- useNotes ?: boolean ;
61+ attackMode ?: MikuNoteMode ;
62+ notes ?: NoteInstance [ ] ;
63+ unlockedNotes ?: NotePickupSubType [ ] ;
64+ erased ?: string [ ] ;
65+ storedCollectibles ?: Set < CollectibleType > ;
66+ }
67+
68+ export interface TaintedMikuSaveData {
69+ attackMode ?: MikuNoteMode ;
5770 notes ?: NoteInstance [ ] ;
5871 unlockedNotes ?: NotePickupSubType [ ] ;
5972 erased ?: string [ ] ;
73+ storedCollectibles ?: CollectibleType [ ] ;
6074}
6175
6276const NAME = "Miku" ;
@@ -65,7 +79,7 @@ const BIRTHRIGHT_DESC = "TODO";
6579const HAIR = Isaac . GetCostumeIdByPath ( "gfx/characters/Character_MikuHead.anm2" ) ;
6680const POCKET_ACTIVE = CollectibleTypeCustom . BROKEN_VOICE ;
6781const NULL_ITEM = CollectibleTypeCustom . MIKU_IDOL ;
68- const NOTE_DROP_CHANCE = 65 ;
82+ const NOTE_DROP_CHANCE = 45 ;
6983
7084const ITEM_REPLACEMENTS : Partial < Record < CollectibleType , CollectibleType > > = {
7185 [ CollectibleType . BRIMSTONE ] : CollectibleTypeCustom . BRIMSTONE_NOTE ,
@@ -92,31 +106,35 @@ export class MikuTaintedCharacter extends Character {
92106 /** Font for the uses text of the notes. */
93107 private font : Font | undefined = undefined ;
94108
95- @CallbackCustom ( ModCallbackCustom . POST_GAME_STARTED_REORDERED , undefined )
109+ @CallbackCustom ( ModCallbackCustom . POST_GAME_STARTED_REORDERED , true )
96110 override onGameStart ( isContinued : boolean ) : void {
97111 if ( ! isContinued || ! mod . HasData ( ) ) {
98112 return ;
99113 }
100114
101- const loaded = jsonDecode ( mod . LoadData ( ) ) as SaveData ;
115+ const loaded = jsonDecode ( mod . LoadData ( ) ) as unknown as ModSaveData ;
102116
103117 Object . assign ( SAVE_DATA , loaded ) ;
104118 }
105119
106120 @Callback ( ModCallback . PRE_GAME_EXIT )
107121 override onGameExit ( ) : void {
108- SAVE_DATA . players = { } ;
122+ SAVE_DATA . mikuBs = { } ;
109123
110124 const players = getPlayersOfType ( PlayerTypeCustom . MIKU_B ) ;
111125 for ( const player of players ) {
112126 const playerData = getData < TaintedMikuData > ( player ) ;
113127
114- const { erased, notes, useNotes } = playerData ;
128+ const { erased, notes, attackMode , storedCollectibles } = playerData ;
115129
116- SAVE_DATA . players [ player . ControllerIndex . toString ( ) ] = {
130+ SAVE_DATA . mikuBs [ player . ControllerIndex . toString ( ) ] = {
131+ attackMode : attackMode ?? MikuNoteMode . GLITCH ,
117132 erased : erased ? [ ...erased ] : [ ] ,
118133 notes : notes ? [ ...notes ] : [ ] ,
119- useNotes : useNotes ?? false ,
134+ unlockedNotes : playerData . unlockedNotes
135+ ? [ ...playerData . unlockedNotes ]
136+ : [ ] ,
137+ storedCollectibles : storedCollectibles ? [ ...storedCollectibles ] : [ ] ,
120138 } ;
121139 }
122140
@@ -138,10 +156,11 @@ export class MikuTaintedCharacter extends Character {
138156 override postPlayerInitFirst ( player : EntityPlayer ) : void {
139157 const playerData = getData < TaintedMikuData > ( player ) ;
140158 playerData . hasIdol = false ;
141- playerData . useNotes = false ;
159+ playerData . attackMode = MikuNoteMode . GLITCH ;
142160 playerData . notes = [ ] ;
143161 playerData . erased = [ ] ;
144162 playerData . unlockedNotes = [ ] ;
163+ playerData . storedCollectibles = new Set ( ) ;
145164
146165 player . AddNullCostume ( HAIR ) ;
147166 Debugger . char ( `${ NAME } (Tainted)` , `Applied null costume: ${ HAIR } ` ) ;
@@ -170,17 +189,25 @@ export class MikuTaintedCharacter extends Character {
170189 return ;
171190 }
172191
173- const saved = SAVE_DATA . players [ player . ControllerIndex . toString ( ) ] ;
192+ const saved = SAVE_DATA . mikuBs [ player . ControllerIndex . toString ( ) ] ;
174193 if ( ! saved ) {
175194 return ;
176195 }
177196
178197 const playerData = getData < TaintedMikuData > ( player ) ;
179- playerData . erased = saved . erased ;
180- playerData . notes = saved . notes ;
181- playerData . useNotes = saved . useNotes ;
182198
183- this . postPlayerInit ( player ) ;
199+ playerData . erased = saved . erased ?? [ ] ;
200+ playerData . notes = saved . notes ?? [ ] ;
201+ playerData . unlockedNotes = saved . unlockedNotes ?? [ ] ;
202+ playerData . attackMode = saved . attackMode ?? MikuNoteMode . GLITCH ;
203+
204+ playerData . storedCollectibles = new Set ( saved . storedCollectibles ?? [ ] ) ;
205+
206+ for ( const collectible of playerData . storedCollectibles ) {
207+ if ( ! player . HasCollectible ( collectible ) ) {
208+ player . AddCollectible ( collectible ) ;
209+ }
210+ }
184211 }
185212
186213 @CallbackCustom (
@@ -190,7 +217,7 @@ export class MikuTaintedCharacter extends Character {
190217 )
191218 override postPlayerUpdate ( player : EntityPlayer ) : void {
192219 const playerData = getData < TaintedMikuData > ( player ) ;
193- const { notes, useNotes } = playerData ;
220+ const { notes } = playerData ;
194221
195222 if ( ! notes || notes . length === 0 ) {
196223 return ;
@@ -201,7 +228,11 @@ export class MikuTaintedCharacter extends Character {
201228 player . ControllerIndex ,
202229 ) ;
203230
204- if ( ! ( useNotes ?? false ) && isDropping && notes . length > 1 ) {
231+ if (
232+ playerData . attackMode === MikuNoteMode . VOICES
233+ && isDropping
234+ && notes . length > 1
235+ ) {
205236 const firstNote = notes . shift ( ) ;
206237 if ( firstNote ) {
207238 notes . push ( firstNote ) ;
@@ -251,9 +282,13 @@ export class MikuTaintedCharacter extends Character {
251282 }
252283
253284 const playerData = getData < TaintedMikuData > ( player ) ;
254- const { notes, useNotes } = playerData ;
285+ const { notes, attackMode } = playerData ;
286+
287+ if ( attackMode === MikuNoteMode . GLITCH ) {
288+ return ;
289+ }
255290
256- if ( ( useNotes ?? false ) || ! notes || notes . length === 0 ) {
291+ if ( ! notes || notes . length === 0 ) {
257292 return ;
258293 }
259294
@@ -311,7 +346,7 @@ export class MikuTaintedCharacter extends Character {
311346
312347 for ( const player of players ) {
313348 const playerData = getData < TaintedMikuData > ( player ) ;
314- const { notes, useNotes } = playerData ;
349+ const { notes, attackMode } = playerData ;
315350 if ( ! notes || notes . length === 0 ) {
316351 continue ;
317352 }
@@ -380,7 +415,7 @@ export class MikuTaintedCharacter extends Character {
380415 : noteConfig . color ;
381416
382417 this . noteSprite . Color =
383- ( useNotes ?? false )
418+ attackMode === MikuNoteMode . GLITCH
384419 ? Color (
385420 baseColor . R * 0.35 ,
386421 baseColor . G * 0.35 ,
@@ -404,7 +439,11 @@ export class MikuTaintedCharacter extends Character {
404439 // Render 'selected' note above player.
405440 const activeNote = notes [ 0 ] ;
406441
407- if ( ! ( useNotes ?? false ) && activeNote && ! isPausedCutscene ) {
442+ if (
443+ attackMode === MikuNoteMode . VOICES
444+ && activeNote
445+ && ! isPausedCutscene
446+ ) {
408447 const noteConfig = NOTE_TYPE_DATA [ activeNote . subType ] ;
409448 const screenPos : Vector = Isaac . WorldToScreen ( player . Position ) ;
410449
@@ -569,21 +608,18 @@ export class MikuTaintedCharacter extends Character {
569608 return undefined ;
570609 }
571610
611+ const data = getData < TaintedMikuData > ( player ) ;
612+
572613 const itemID = pickup . SubType as CollectibleType ;
573614 const synergyNote = ITEM_SYNERGIES [ itemID ] ;
574615 const replaceItem = ITEM_REPLACEMENTS [ itemID ] ;
575616
576- // FIXME: Solution until I find a better one...
577- if ( itemID === CollectibleType . LUDOVICO_TECHNIQUE ) {
578- player . AddCollectible ( CollectibleType . TECH_5 ) ;
579- return undefined ;
580- }
617+ this . checkDisabledItem ( player , itemID ) ;
581618
582619 if ( synergyNote === undefined || replaceItem === undefined ) {
583620 return undefined ;
584621 }
585622
586- const data = getData < TaintedMikuData > ( player ) ;
587623 data . unlockedNotes ??= [ ] ;
588624
589625 if ( ! data . unlockedNotes . includes ( synergyNote ) ) {
@@ -644,6 +680,14 @@ export class MikuTaintedCharacter extends Character {
644680 ( ) => anyPlayerIs ( PlayerTypeCustom . MIKU_B ) ,
645681 ) ;
646682
683+ appendToDescription (
684+ eid ,
685+ "Mom's Knife" ,
686+ PlayerTypeCustom . MIKU_B ,
687+ `#{{Player${ PlayerTypeCustom . MIKU_B } }} {{Warning}} Tainted Miku can use the knife only in her glitched mode` ,
688+ ( ) => anyPlayerIs ( PlayerTypeCustom . MIKU_B ) ,
689+ ) ;
690+
647691 appendToDescription (
648692 eid ,
649693 "Dr. Fetus" ,
@@ -652,4 +696,20 @@ export class MikuTaintedCharacter extends Character {
652696 ( ) => anyPlayerIs ( PlayerTypeCustom . MIKU_B ) ,
653697 ) ;
654698 }
699+
700+ private checkDisabledItem (
701+ player : EntityPlayer ,
702+ itemID : CollectibleType ,
703+ ) : void {
704+ const data = getData < TaintedMikuData > ( player ) ;
705+
706+ if ( isNoteItemDisabled ( itemID ) && data . attackMode !== MikuNoteMode . GLITCH ) {
707+ setMikuAttackMode ( player , MikuNoteMode . GLITCH ) ;
708+
709+ Debugger . char (
710+ NAME ,
711+ `Switched to Glitch mode because ${ itemID } is disabled in Notes mode.` ,
712+ ) ;
713+ }
714+ }
655715}
0 commit comments