Skip to content

Commit 06043f1

Browse files
committed
Miku: clamp invalid fire rate values
1 parent 00e7c69 commit 06043f1

6 files changed

Lines changed: 25 additions & 90 deletions

File tree

BUGS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
- [x] When using 'Monstros Lung' as Tainted Miku your tear rate will sky rocket to 120. Reported by DoomSlammer
1111
- [x] Using a Chaos Card as either version of Hatsune Miku instead throws a default musical note projectile (colorful/rainbow for regular, gray-scale/monochrome for tainted) that doesn't instantly kill entities. Reported by RDW
1212
- [ ] Subtle visual glitch on the first frame of a Glitch Note without a variant.
13+
- [ ] Fix Spirit Sword and Mom's Knife not working with Tainted Miku's note system switch.

src/characters/Character.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { EIDExtended } from "../compat/EID";
22
import { Feature } from "../Feature";
33

4-
export interface PlayerData {
5-
fireDelayInit?: boolean;
6-
}
4+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface
5+
export interface PlayerData {}
76

87
/** Abstract base class representing a custom character. */
98
export abstract class Character extends Feature {

src/characters/Miku/MikuCharacter.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ import {
1212
} from "isaacscript-common";
1313
import type { EIDExtended } from "../../compat/EID";
1414
import { CollectibleTypeCustom } from "../../items/enum";
15-
import { setFireRate } from "../../util/calc";
1615
import { getData } from "../../util/data";
1716
import { Debugger } from "../../util/debug";
1817
import type { PlayerData } from "../Character";
1918
import { Character } from "../Character";
20-
import { isMiku, PlayerTypeCustom } from "../enum";
19+
import { PlayerTypeCustom } from "../enum";
2120

2221
export interface MikuPlayerData extends PlayerData {
2322
hasIdol?: boolean;
@@ -30,11 +29,11 @@ const BIRTHRIGHT_DESC =
3029
const ACTIVE = CollectibleTypeCustom.MICROPHONE;
3130
const NULL_ITEM = CollectibleTypeCustom.MIKU_IDOL;
3231
const HAIR = Isaac.GetCostumeIdByPath("gfx/characters/Character_MikuHead.anm2");
33-
const TEARS_STAT = 0.5;
3432

3533
export const MIKU_STATS = new ReadonlyMap<CacheFlag, number>([
3634
[CacheFlag.SPEED, 1.2],
3735
[CacheFlag.DAMAGE, 2.8],
36+
[CacheFlag.FIRE_DELAY, 3.33],
3837
]);
3938

4039
export class MikuCharacter extends Character {
@@ -74,15 +73,6 @@ export class MikuCharacter extends Character {
7473
super.postPlayerInit(player);
7574
}
7675

77-
@Callback(ModCallback.EVALUATE_CACHE, CacheFlag.FIRE_DELAY)
78-
override cacheFireDelay(player: EntityPlayer): void {
79-
if (!isMiku(player)) {
80-
return;
81-
}
82-
83-
setFireRate<MikuPlayerData>(player, TEARS_STAT);
84-
}
85-
8676
/**
8777
* Sets up **External Item Descriptions (EID)** compatibility for Miku.
8878
*

src/characters/Miku/MikuTaintedCharacter.ts

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ import {
3939
import type { GlitchNoteTearData } from "../../entities/tears/GlitchNoteTear/GlitchNoteTear";
4040
import { CollectibleTypeCustom } from "../../items/enum";
4141
import { mod } from "../../mod";
42-
import { getFireRateMultiplier, setFireRate } from "../../util/calc";
42+
import { maxFireDelayToTears, tearsToMaxFireDelay } from "../../util/calc";
43+
import { ISAAC_STATS } from "../../util/const";
4344
import { updateCollectibleCostumes } from "../../util/costumes";
4445
import { getData } from "../../util/data";
4546
import { Debugger } from "../../util/debug";
@@ -64,7 +65,6 @@ const BIRTHRIGHT_DESC = "TODO";
6465
const HAIR = Isaac.GetCostumeIdByPath("gfx/characters/Character_MikuHead.anm2");
6566
const POCKET_ACTIVE = CollectibleTypeCustom.BROKEN_VOICE;
6667
const NULL_ITEM = CollectibleTypeCustom.MIKU_IDOL;
67-
const TEARS_STAT = -0.6;
6868
const NOTE_DROP_CHANCE = 65;
6969

7070
const ITEM_REPLACEMENTS: Partial<Record<CollectibleType, CollectibleType>> = {
@@ -77,14 +77,11 @@ const ITEM_COSTUMES: Partial<Record<CollectibleType, CollectibleType>> = {
7777
[CollectibleTypeCustom.DR_FETUS_NOTE]: CollectibleType.DR_FETUS,
7878
} as const;
7979

80-
const FIRE_RATE_ITEM_MULTIPLIERS: Partial<Record<CollectibleType, number>> = {
81-
[CollectibleType.MONSTROS_LUNG]: 0.35,
82-
} as const;
83-
8480
export const MIKU_B_STATS = new ReadonlyMap<CacheFlag, float>([
85-
[CacheFlag.DAMAGE, 3.8],
86-
[CacheFlag.LUCK, -1],
81+
[CacheFlag.DAMAGE, 4.35],
82+
[CacheFlag.LUCK, -1.5],
8783
[CacheFlag.COLOR, 2],
84+
[CacheFlag.FIRE_DELAY, 2.13],
8885
]);
8986

9087
export class MikuTaintedCharacter extends Character {
@@ -140,21 +137,18 @@ export class MikuTaintedCharacter extends Character {
140137
)
141138
override postPlayerInitFirst(player: EntityPlayer): void {
142139
const playerData = getData<TaintedMikuData>(player);
143-
playerData.erased = [];
144-
playerData.notes = [];
140+
playerData.hasIdol = false;
145141
playerData.useNotes = false;
142+
playerData.notes = [];
143+
playerData.erased = [];
146144
playerData.unlockedNotes = [];
147-
playerData.hasIdol = false;
148145

149146
player.AddNullCostume(HAIR);
150147
Debugger.char(`${NAME} (Tainted)`, `Applied null costume: ${HAIR}`);
151148

152-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
153-
if (!playerData.hasIdol) {
154-
player.AddCollectible(NULL_ITEM, 0);
155-
playerData.hasIdol = true;
156-
Debugger.char(NAME, `Applied null item: ${NULL_ITEM}.`);
157-
}
149+
player.AddCollectible(NULL_ITEM, 0);
150+
playerData.hasIdol = true;
151+
Debugger.char(NAME, `Applied null item: ${NULL_ITEM}.`);
158152

159153
if (!player.HasCollectible(POCKET_ACTIVE)) {
160154
player.SetPocketActiveItem(POCKET_ACTIVE, ActiveSlot.POCKET, false);
@@ -229,16 +223,10 @@ export class MikuTaintedCharacter extends Character {
229223
return;
230224
}
231225

232-
const multiplier = getFireRateMultiplier(
233-
player,
234-
FIRE_RATE_ITEM_MULTIPLIERS,
235-
);
236-
237-
if (multiplier === -1) {
238-
return;
226+
const tears = maxFireDelayToTears(player.MaxFireDelay);
227+
if (tears >= ISAAC_STATS.TEARS_THRESHOLD) {
228+
player.MaxFireDelay = tearsToMaxFireDelay(0.1);
239229
}
240-
241-
setFireRate<TaintedMikuData>(player, TEARS_STAT, multiplier);
242230
}
243231

244232
@Callback(ModCallback.POST_TEAR_INIT)

src/util/calc.ts

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,4 @@
1-
import type { CollectibleType } from "isaac-typescript-definitions";
2-
import type { PlayerData } from "../characters/Character";
3-
import { getData } from "./data";
1+
export const maxFireDelayToTears = (maxFireDelay: number): number =>
2+
30 / (maxFireDelay + 1);
43

5-
const tearsToMaxFireDelay = (tears: number): number => 30 / tears - 1;
6-
7-
export const getFireRateMultiplier = (
8-
player: EntityPlayer,
9-
entries: Partial<Record<CollectibleType, number>>,
10-
): number => {
11-
let multiplier = 1;
12-
13-
for (const [item, value] of Object.entries(entries) as Array<
14-
[string, number | undefined]
15-
>) {
16-
const collectible = Number(item) as CollectibleType;
17-
18-
if (value !== undefined && player.HasCollectible(collectible)) {
19-
multiplier *= value;
20-
}
21-
}
22-
23-
return multiplier;
24-
};
25-
26-
/**
27-
* Sets base stat the MaxFireDelay for the player. It calculates the tears value to the fire delay.
28-
*
29-
* @param player The player the fire rate should be applied too.
30-
* @param tears Tear Rate that should be applied.
31-
*/
32-
export const setFireRate = <T extends PlayerData>(
33-
player: EntityPlayer,
34-
tears: number,
35-
multiplier = 1,
36-
): void => {
37-
const data = getData<T>(player);
38-
39-
if (!(data.fireDelayInit ?? false)) {
40-
const currentTears = 30 / (player.MaxFireDelay + 1);
41-
42-
const modifiedTears = tears * multiplier;
43-
44-
const newTears = currentTears + modifiedTears;
45-
46-
const clampedTears = Math.max(0.1, newTears);
47-
48-
player.MaxFireDelay = tearsToMaxFireDelay(clampedTears);
49-
}
50-
};
4+
export const tearsToMaxFireDelay = (tears: number): number => 30 / tears - 1;

src/util/const.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const ISAAC_STATS = {
2+
TEARS_THRESHOLD: 120,
3+
} as const;

0 commit comments

Comments
 (0)