Skip to content

Commit

Permalink
Prepare Funker Selector for Funkin' v0.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
AbnormalPoof committed Feb 20, 2025
1 parent 2628bc3 commit 064f073
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 150 deletions.
12 changes: 12 additions & 0 deletions _merge/data/stages/tankmanBattlefield.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"op": "replace",
"path": "/characters/bf/zIndex",
"value": 150
},
{
"op": "replace",
"path": "/characters/dad/zIndex",
"value": 150
}
]
111 changes: 46 additions & 65 deletions scripts/modules/FS_CharacterDataHandler.hxc
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import funkin.modding.module.ModuleHandler;
import funkin.play.character.CharacterDataParser;
import funkin.save.Save;
import funkin.util.MemoryUtil;
import funkin.util.SerializerUtil;
import funkin.util.ReflectUtil;
import funkin.util.assets.DataAssets;
import haxe.ds.StringMap;
import haxe.ds.Either;
import hxjsonast.Parser;
import hxjsonast.Tools;
import thx.Dynamics;
import thx.Objects;
import thx.Types;

/**
* A module responsible for handling character caches and data.
Expand Down Expand Up @@ -41,8 +46,6 @@ class FS_CharacterDataHandler extends Module

/**
* The default character data. This is used as base.
*
* TODO: Use this in `parseJSONData()` when v0.6.0 is out.
*/
var FS_DEFAULT_CHARACTER_DATA:FS_characterData =
{
Expand All @@ -55,16 +58,16 @@ class FS_CharacterDataHandler extends Module
unlockMethod: null,
description:
{
text: "This is placeholder data! If you're somehow reading this then you probably did something really wrong.",
unlockCondition: "This is placeholder data! If you're somehow reading this then you probably did something really wrong."
text: "No description was specified in the JSON file.",
unlockCondition: "No unlock condition was specified in the JSON file."
},
characterMenu:
{
position: [200, 0],
scale: 1.0,
isPixel: false,
flipX: false,
selectedAnim: null
selectedAnim: "hey"
},
suffixes:
{
Expand Down Expand Up @@ -189,46 +192,19 @@ class FS_CharacterDataHandler extends Module
// Check if the file exists
if (Assets.exists(filePath))
{
var parsedData = SerializerUtil.fromJSON(Assets.getText(filePath));
var parsedData = Tools.getValue(Parser.parse(Assets.getText(filePath), filePath));

if (parsedData == null)
{
trace("[Funker Selector] Failed to parse " + Assets.getPath(filePath) + "!");
return null;
}

// I basically just put this all in one big object so that I don't have to do a bunch of null checks lol
var result:FS_characterData =
{
version: nullCoalesce(parsedData?.version, "1.0.0"),
characterID: nullCoalesce(parsedData?.characterID, null),
characterType: nullCoalesce(parsedData?.characterType, "bf"),
mustUnlock: nullCoalesce(parsedData?.mustUnlock, false),
voiceID: nullCoalesce(parsedData?.voiceID, parsedData?.characterID),
introSwapFrame: nullCoalesce(parsedData?.introSwapFrame, 3),
unlockMethod: nullCoalesce(parsedData?.unlockMethod, null),
description:
{
text: nullCoalesce(parsedData?.description?.text, "No description was specified in the JSON file."),
unlockCondition: nullCoalesce(parsedData?.description?.unlockCondition, "No unlock condition was specified in the JSON file.")
},
characterMenu:
{
position: nullCoalesce(parsedData?.characterMenu?.position, [0, 0]),
scale: calculateCharacterScale(parsedData.characterMenu?.scale, parsedData?.characterMenu?.isPixel),
isPixel: nullCoalesce(parsedData?.characterMenu?.isPixel, false),
flipX: nullCoalesce(parsedData?.characterMenu?.flipX, false),
selectedAnim: nullCoalesce(parsedData?.characterMenu?.selectedAnim, "hey")
},
// We still set suffixes to null to account for scripts.
suffixes:
{
gameOverMusic: nullCoalesce(parsedData?.suffixes?.gameOverMusic, null),
blueBall: nullCoalesce(parsedData?.suffixes?.blueBall, null),
pauseMusic: nullCoalesce(parsedData?.suffixes?.pauseMusic, null)
},
characterVariations: nullCoalesce(parsedData?.characterVariations, [])
};
// Merge the parsed data with the default character data, basically layering the parsed object on top of the default one.
var result = mergeStructures(Objects.clone(FS_DEFAULT_CHARACTER_DATA), parsedData);

// Manually calculate the scale of the character.
result.characterMenu.scale = calculateCharacterScale(result.characterMenu.scale, result.characterMenu.isPixel);

return result;
}
Expand All @@ -237,22 +213,42 @@ class FS_CharacterDataHandler extends Module
}

/**
* Calculates the scale of the character.
* @return The calculated scale value.
* Merges two structures together.
* I would use `Objects.deepCombine` instead, but it doesn't do null-checking.
*
* @param source The source structure. This is the "base", so to speak.
* @param target The target structure. This is overlayed on top of `source`.
* @return The merged structure.
*/
function calculateCharacterScale(scale:Float, isPixel:Bool):Float
function mergeStructures(source:Dynamic, target:Dynamic):Dynamic
{
if (scale == null)
{
return 1;
}
var mergedObject:Dynamic = Objects.clone(source);

if (isPixel)
for (field in ReflectUtil.getAnonymousFieldsOf(mergedObject))
{
return scale * 6;
var sourceValue = ReflectUtil.getAnonymousField(source, field);
var targetValue = ReflectUtil.getAnonymousField(target, field);

if (Types.isAnonymousObject(sourceValue) && Types.isAnonymousObject(targetValue))
{
ReflectUtil.setAnonymousField(mergedObject, field, mergeStructures(sourceValue, targetValue));
}
else if (targetValue != null)
{
ReflectUtil.setAnonymousField(mergedObject, field, targetValue);
}
}

return scale;
return mergedObject;
}

/**
* Calculates the scale of the character.
* @return The calculated scale value.
*/
function calculateCharacterScale(scale:Float, isPixel:Bool):Float
{
return isPixel ? (scale * 6) : scale;
}

/**
Expand Down Expand Up @@ -316,7 +312,7 @@ class FS_CharacterDataHandler extends Module
return false;
}
default:
trace("[Funker Selector] Unlock Method " + unlockMethod.type + " not recognized.");
trace("[Funker Selector] Unlock Method " + unlockMethod?.type + " not recognized.");
return false;
}
}
Expand Down Expand Up @@ -487,21 +483,6 @@ class FS_CharacterDataHandler extends Module
}
MemoryUtil.collect(true);
}

/**
* HELPER FUNCTIONS
* These are functions that are simply nice to have. They make the code cleaner.
*/
/**
* Imitates null coalescing since HScript doesn't support it yet.
* @param value The value to check.
* @param fallback The fallback value.
* @return The value if it's not null, otherwise the fallback value.
*/
public function nullCoalesce(value:Dynamic, fallback:Dynamic):Dynamic
{
return value != null ? value : fallback;
}
}

/**
Expand Down
39 changes: 8 additions & 31 deletions scripts/modules/FS_CoreModule.hxc
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ class FS_CoreModule extends Module
{
var fs_characterData:Dynamic = FS_CharacterDataHandler.scriptCall('getCharacterData', [characterID]);
var oldChar:BaseCharacter = null;

var voiceID:String = fs_characterData?.voiceID;
var variationSuffix:String = PlayState.instance.currentVariation != 'default' ? '-' + PlayState.instance.currentVariation : '';
var voiceFile:String = Paths.voices(PlayState.instance.currentSong.id, '-' + voiceID + variationSuffix);
Expand Down Expand Up @@ -224,12 +223,12 @@ class FS_CoreModule extends Module
}
else
{
variationMatch = true; // Assume true if songVariation does not exist.
variationMatch = true; // Assume true if `songVariation` does not exist.
}

if (songMatch && variationMatch)
{
fs_characterData = nullCoalesce(FS_CharacterDataHandler.scriptCall('getCharacterData', [variation.characterID]), fs_characterData);
fs_characterData = FS_CharacterDataHandler.scriptCall('getCharacterData', [variation.characterID]) ?? fs_characterData;

if (FS_CharacterDataHandler.scriptCall('charJSONCheck', [variation.characterID]))
{
Expand Down Expand Up @@ -270,9 +269,9 @@ class FS_CoreModule extends Module

if (characterType == CharacterType.BF)
{
PauseSubState.musicSuffix = nullCoalesce(fs_characterData.suffixes.pauseMusic, PauseSubState.musicSuffix);
GameOverSubState.musicSuffix = nullCoalesce(fs_characterData.suffixes.gameOverMusic, GameOverSubState.musicSuffix);
GameOverSubState.blueBallSuffix = nullCoalesce(fs_characterData.suffixes.blueBall, GameOverSubState.blueBallSuffix);
PauseSubState.musicSuffix = fs_characterData?.suffixes?.pauseMusic ?? PauseSubState.musicSuffix;
GameOverSubState.musicSuffix = fs_characterData?.suffixes?.gameOverMusic ?? GameOverSubState.musicSuffix;
GameOverSubState.blueBallSuffix = fs_characterData?.suffixes?.blueBall ?? GameOverSubState.blueBallSuffix;
}

trace('[Funker Selector] Suffixes\n\nPause Music: ' + PauseSubState.musicSuffix + '\nGame Over Music: ' + GameOverSubState.musicSuffix
Expand All @@ -290,8 +289,8 @@ class FS_CoreModule extends Module
var result:VoicesGroup = new VoicesGroup();
var songVoices:Array<String> = currentChart.buildVoiceList();

result.addPlayerVoice(FunkinSound.load(nullCoalesce(voiceList[0], songVoices[0])));
result.addOpponentVoice(FunkinSound.load(nullCoalesce(voiceList[1], songVoices[1])));
result.addPlayerVoice(FunkinSound.load(voiceList[0] ?? songVoices[0]));
result.addOpponentVoice(FunkinSound.load(voiceList[1] ?? songVoices[1]));

result.playerVoicesOffset = currentChart.offsets.getVocalOffset(currentChart.characters.player);
result.opponentVoicesOffset = currentChart.offsets.getVocalOffset(currentChart.characters.opponent);
Expand Down Expand Up @@ -426,8 +425,6 @@ class FS_CoreModule extends Module
var prefs = event.targetState.pages.get("preferences");
if (prefs != null)
{
prefs.add(prefs.items.createItem(120, 120 * prefs.items.length + 30, "-- FUNKER SELECTOR --", "bold", () -> {})).fireInstantly = true;

prefs.createPrefItemCheckbox("Simplify UI", "Simplifies the UI and disables character sprite caching.", (value) -> {
FS_SaveDataHandler.scriptCall('setPreference', ["potatoMode", value]);
}, FS_SaveDataHandler.scriptCall('getPreference', ["potatoMode"]));
Expand Down Expand Up @@ -463,8 +460,6 @@ class FS_CoreModule extends Module
(value) -> {
FS_SaveDataHandler.scriptCall('setPreference', ["djSwapping", value]);
}, FS_SaveDataHandler.scriptCall('getPreference', ["djSwapping"]));

prefs.add(prefs.items.createItem(120, 120 * prefs.items.length, "-------------------", "bold", () -> {})).fireInstantly = true;
}
}
}
Expand Down Expand Up @@ -499,14 +494,8 @@ class FS_CoreModule extends Module
|| PlayState.instance.isMinimalMode
|| PlayState.instance.isChartingMode) return;

// Reset the voiceList, swap out the characters, and
// replace vocals if any are found.

// Setting voiceList to null in order to reset the voices,
// since module variables persist between state changes.
voiceList = [null, null];

// Replace and swap out the characters.
replaceChar(FS_SaveDataHandler.scriptGet('saveData').characterIDs.bf, CharacterType.BF);
replaceChar(FS_SaveDataHandler.scriptGet('saveData').characterIDs.gf, CharacterType.GF);
replaceChar(FS_SaveDataHandler.scriptGet('saveData').characterIDs.dad, CharacterType.DAD);
Expand All @@ -531,18 +520,6 @@ class FS_CoreModule extends Module
*/
public function isDebugBuild():Bool
{
// Debug builds use a different background color.
return Application.current.window.context.attributes.background == 0xFFFF00FF;
}

/**
* Imitates null coalescing since HScript doesn't support it yet.
* @param value The value to check.
* @param fallback The fallback value.
* @return The value if it's not null, otherwise the fallback value.
*/
public function nullCoalesce(value:Dynamic, fallback:Dynamic):Dynamic
{
return value != null ? value : fallback;
return Constants.DEBUG_BUILD;
}
}
19 changes: 2 additions & 17 deletions scripts/modules/FS_FreeplayDJReplace.hxc
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class DJReplace extends Module
var freeplayDJ:FreeplayDJ = FlxG.state.subState.dj;
var characterID:String = PlayerRegistry.instance.isCharacterOwned(saveData.characterIDs.bf) ? PlayerRegistry.instance.getCharacterOwnerId(saveData.characterIDs.bf) : FlxG.state.subState.currentCharacterId;
var playableChar:PlayableCharacter = PlayerRegistry.instance.fetchEntry(characterID);
var playableCharData:Dynamic = playableChar != null ? playableChar.getFreeplayDJData() : null;
var playableCharData:Dynamic = playableChar.getFreeplayDJData() ?? null;

var fs_characterData:Dynamic = ModuleHandler.getModule("FS_CharacterDataHandler").scriptCall('getCharacterData', [saveData.characterIDs.bf]);
var introFrame:Int = fs_characterData.introSwapFrame;
Expand Down Expand Up @@ -152,7 +152,7 @@ class DJReplace extends Module
var freeplayDJ:FreeplayDJ = FlxG.state.subState.dj;
var characterID:String = PlayerRegistry.instance.isCharacterOwned(saveData.characterIDs.bf) ? PlayerRegistry.instance.getCharacterOwnerId(saveData.characterIDs.bf) : FlxG.state.subState.currentCharacterId;
var playableChar:PlayableCharacter = PlayerRegistry.instance.fetchEntry(characterID);
var playableCharData:Dynamic = playableChar != null ? playableChar.getFreeplayDJData() : null;
var playableCharData:Dynamic = playableChar.getFreeplayDJData() ?? null;

var startTime = TimerUtil.start();

Expand Down Expand Up @@ -254,19 +254,4 @@ class DJReplace extends Module

textObject.speed = speed;
}

/**
* HELPER FUNCTIONS
* These are functions that are simply nice to have. They make the code cleaner.
*/
/**
* Imitates null coalescing since HScript doesn't support it yet.
* @param value The value to check.
* @param fallback The fallback value.
* @return The value if it's not null, otherwise the fallback value.
*/
public function nullCoalesce(value:Dynamic, fallback:Dynamic):Dynamic
{
return value != null ? value : fallback;
}
}
27 changes: 8 additions & 19 deletions scripts/modules/FS_SaveDataHandler.hxc
Original file line number Diff line number Diff line change
Expand Up @@ -165,21 +165,21 @@ class FS_SaveDataHandler extends Module
version: FS_SAVE_DATA_VERSION,
characterIDs:
{
bf: nullCoalesce(oldCharacterIDs?.bf, 'default'),
gf: nullCoalesce(oldCharacterIDs?.gf, 'default'),
dad: nullCoalesce(oldCharacterIDs?.dad, 'default')
bf: oldCharacterIDs?.bf ?? 'default',
gf: oldCharacterIDs?.gf ?? 'default',
dad: oldCharacterIDs?.dad ?? 'default'
},
preferences: new StringMap(),
seenUnlocks: nullCoalesce(oldSeenUnlocks, [])
seenUnlocks: oldSeenUnlocks ?? []
};

// Settings is a special case...
var settings:Dynamic =
{
potatoMode: nullCoalesce(oldPrefs?.potatoMode, false),
preloadSprites: nullCoalesce(oldPrefs?.preloadSprites, false),
preferredSFX: nullCoalesce(oldPrefs?.preferredSFX, 'funkin'),
djSwapping: nullCoalesce(oldPrefs?.djSwapping, true)
potatoMode: oldPrefs?.potatoMode ?? false,
preloadSprites: oldPrefs?.preloadSprites ?? false,
preferredSFX: oldPrefs?.preferredSFX ?? 'funkin',
djSwapping: oldPrefs?.djSwapping ?? true
};

for (key in ReflectUtil.getAnonymousFieldsOf(settings))
Expand Down Expand Up @@ -301,15 +301,4 @@ class FS_SaveDataHandler extends Module
saveData.preferences[key] = value;
writeSaveData();
}

/**
* Imitates null coalescing since HScript doesn't support it yet.
* @param value The value to check.
* @param fallback The fallback value.
* @return The value if it's not null, otherwise the fallback value.
*/
public function nullCoalesce(value:Dynamic, fallback:Dynamic):Dynamic
{
return value != null ? value : fallback;
}
}
Loading

0 comments on commit 064f073

Please sign in to comment.