mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-14 19:25:16 -05:00
Lots of reported bugs fixed.
This commit is contained in:
parent
d47c5f5765
commit
cdab8753ea
14 changed files with 147 additions and 26 deletions
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
|
@ -23,6 +23,12 @@
|
||||||
"name": "Haxe Eval",
|
"name": "Haxe Eval",
|
||||||
"type": "haxe-eval",
|
"type": "haxe-eval",
|
||||||
"request": "launch"
|
"request": "launch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Attaches the debugger to an already running game
|
||||||
|
"name": "HXCPP - Attach",
|
||||||
|
"type": "hxcpp",
|
||||||
|
"request": "attach"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,7 +240,7 @@ class PauseSubState extends MusicBeatSubState
|
||||||
|
|
||||||
case 'Exit to Chart Editor':
|
case 'Exit to Chart Editor':
|
||||||
this.close();
|
this.close();
|
||||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
|
||||||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
PlayState.instance.close(); // This only works because PlayState is a substate!
|
||||||
|
|
||||||
case 'BACK':
|
case 'BACK':
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package funkin.data;
|
package funkin.data;
|
||||||
|
|
||||||
import funkin.data.song.importer.FNFLegacyData.LegacyNote;
|
import funkin.data.song.importer.FNFLegacyData.LegacyNote;
|
||||||
import hxjsonast.Json;
|
|
||||||
import hxjsonast.Tools;
|
|
||||||
import hxjsonast.Json.JObjectField;
|
|
||||||
import haxe.ds.Either;
|
|
||||||
import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection;
|
|
||||||
import funkin.data.song.importer.FNFLegacyData.LegacyNoteData;
|
import funkin.data.song.importer.FNFLegacyData.LegacyNoteData;
|
||||||
|
import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection;
|
||||||
import funkin.data.song.importer.FNFLegacyData.LegacyScrollSpeeds;
|
import funkin.data.song.importer.FNFLegacyData.LegacyScrollSpeeds;
|
||||||
|
import haxe.ds.Either;
|
||||||
|
import hxjsonast.Json;
|
||||||
|
import hxjsonast.Json.JObjectField;
|
||||||
|
import hxjsonast.Tools;
|
||||||
|
import thx.semver.Version;
|
||||||
|
import thx.semver.VersionRule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `json2object` has an annotation `@:jcustomparse` which allows for mutation of parsed values.
|
* `json2object` has an annotation `@:jcustomparse` which allows for mutation of parsed values.
|
||||||
|
@ -23,7 +25,8 @@ class DataParse
|
||||||
* `@:jcustomparse(funkin.data.DataParse.stringNotEmpty)`
|
* `@:jcustomparse(funkin.data.DataParse.stringNotEmpty)`
|
||||||
* @param json Contains the `pos` and `value` of the property.
|
* @param json Contains the `pos` and `value` of the property.
|
||||||
* @param name The name of the property.
|
* @param name The name of the property.
|
||||||
* @throws If the property is not a string or is empty.
|
* @throws Error If the property is not a string or is empty.
|
||||||
|
* @return The string value.
|
||||||
*/
|
*/
|
||||||
public static function stringNotEmpty(json:Json, name:String):String
|
public static function stringNotEmpty(json:Json, name:String):String
|
||||||
{
|
{
|
||||||
|
@ -37,6 +40,42 @@ class DataParse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `@:jcustomparse(funkin.data.DataParse.semverVersion)`
|
||||||
|
* @param json Contains the `pos` and `value` of the property.
|
||||||
|
* @param name The name of the property.
|
||||||
|
* @return The value of the property as a `thx.semver.Version`.
|
||||||
|
*/
|
||||||
|
public static function semverVersion(json:Json, name:String):Version
|
||||||
|
{
|
||||||
|
switch (json.value)
|
||||||
|
{
|
||||||
|
case JString(s):
|
||||||
|
if (s == "") throw 'Expected version property $name to be non-empty.';
|
||||||
|
return s;
|
||||||
|
default:
|
||||||
|
throw 'Expected version property $name to be a string, but it was ${json.value}.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `@:jcustomparse(funkin.data.DataParse.semverVersionRule)`
|
||||||
|
* @param json Contains the `pos` and `value` of the property.
|
||||||
|
* @param name The name of the property.
|
||||||
|
* @return The value of the property as a `thx.semver.VersionRule`.
|
||||||
|
*/
|
||||||
|
public static function semverVersionRule(json:Json, name:String):VersionRule
|
||||||
|
{
|
||||||
|
switch (json.value)
|
||||||
|
{
|
||||||
|
case JString(s):
|
||||||
|
if (s == "") throw 'Expected version rule property $name to be non-empty.';
|
||||||
|
return s;
|
||||||
|
default:
|
||||||
|
throw 'Expected version rule property $name to be a string, but it was ${json.value}.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parser which outputs a Dynamic value, either a object or something else.
|
* Parser which outputs a Dynamic value, either a object or something else.
|
||||||
* @param json
|
* @param json
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package funkin.data;
|
package funkin.data;
|
||||||
|
|
||||||
import funkin.util.SerializerUtil;
|
import funkin.util.SerializerUtil;
|
||||||
|
import thx.semver.Version;
|
||||||
|
import thx.semver.VersionRule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `json2object` has an annotation `@:jcustomwrite` which allows for custom serialization of values to be written to JSON.
|
* `json2object` has an annotation `@:jcustomwrite` which allows for custom serialization of values to be written to JSON.
|
||||||
|
@ -9,9 +11,30 @@ import funkin.util.SerializerUtil;
|
||||||
*/
|
*/
|
||||||
class DataWrite
|
class DataWrite
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* `@:jcustomwrite(funkin.data.DataWrite.dynamicValue)`
|
||||||
|
* @param value
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
public static function dynamicValue(value:Dynamic):String
|
public static function dynamicValue(value:Dynamic):String
|
||||||
{
|
{
|
||||||
// Is this cheating? Yes. Do I care? No.
|
// Is this cheating? Yes. Do I care? No.
|
||||||
return SerializerUtil.toJSON(value);
|
return SerializerUtil.toJSON(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `@:jcustomwrite(funkin.data.DataWrite.semverVersion)`
|
||||||
|
*/
|
||||||
|
public static function semverVersion(value:Version):String
|
||||||
|
{
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `@:jcustomwrite(funkin.data.DataWrite.semverVersionRule)`
|
||||||
|
*/
|
||||||
|
public static function semverVersionRule(value:VersionRule):String
|
||||||
|
{
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ class SongMetadata
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
|
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
|
||||||
|
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||||
|
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||||
public var version:Version;
|
public var version:Version;
|
||||||
|
|
||||||
@:default("Unknown")
|
@:default("Unknown")
|
||||||
|
@ -203,6 +205,8 @@ class SongMusicData
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
|
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
|
||||||
|
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||||
|
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||||
public var version:Version;
|
public var version:Version;
|
||||||
|
|
||||||
@:default("Unknown")
|
@:default("Unknown")
|
||||||
|
@ -367,6 +371,8 @@ class SongCharacterData
|
||||||
class SongChartData
|
class SongChartData
|
||||||
{
|
{
|
||||||
@:default(funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION)
|
@:default(funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION)
|
||||||
|
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||||
|
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||||
public var version:Version;
|
public var version:Version;
|
||||||
|
|
||||||
public var scrollSpeed:Map<String, Float>;
|
public var scrollSpeed:Map<String, Float>;
|
||||||
|
|
|
@ -246,7 +246,8 @@ class SongDataUtils
|
||||||
|
|
||||||
typedef SongClipboardItems =
|
typedef SongClipboardItems =
|
||||||
{
|
{
|
||||||
?valid:Bool,
|
@:optional
|
||||||
notes:Array<SongNoteData>,
|
var valid:Bool;
|
||||||
events:Array<SongEventData>
|
var notes:Array<SongNoteData>;
|
||||||
|
var events:Array<SongEventData>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ class SongMetadata_v2_0_0
|
||||||
// ==========
|
// ==========
|
||||||
// UNMODIFIED VALUES
|
// UNMODIFIED VALUES
|
||||||
// ==========
|
// ==========
|
||||||
|
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||||
|
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||||
public var version:Version;
|
public var version:Version;
|
||||||
|
|
||||||
@:default("Unknown")
|
@:default("Unknown")
|
||||||
|
|
|
@ -129,7 +129,6 @@ class HealthIcon extends FlxSprite
|
||||||
if (value == characterId) return value;
|
if (value == characterId) return value;
|
||||||
|
|
||||||
characterId = value ?? Constants.DEFAULT_HEALTH_ICON;
|
characterId = value ?? Constants.DEFAULT_HEALTH_ICON;
|
||||||
loadCharacter(characterId);
|
|
||||||
return characterId;
|
return characterId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +137,6 @@ class HealthIcon extends FlxSprite
|
||||||
if (value == isPixel) return value;
|
if (value == isPixel) return value;
|
||||||
|
|
||||||
isPixel = value;
|
isPixel = value;
|
||||||
loadCharacter(characterId);
|
|
||||||
return isPixel;
|
return isPixel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,8 +163,11 @@ class HealthIcon extends FlxSprite
|
||||||
{
|
{
|
||||||
if (data == null)
|
if (data == null)
|
||||||
{
|
{
|
||||||
this.isPixel = false;
|
|
||||||
this.characterId = Constants.DEFAULT_HEALTH_ICON;
|
this.characterId = Constants.DEFAULT_HEALTH_ICON;
|
||||||
|
this.isPixel = false;
|
||||||
|
|
||||||
|
loadCharacter(characterId);
|
||||||
|
|
||||||
this.size.set(1.0, 1.0);
|
this.size.set(1.0, 1.0);
|
||||||
this.offset.x = 0.0;
|
this.offset.x = 0.0;
|
||||||
this.offset.y = 0.0;
|
this.offset.y = 0.0;
|
||||||
|
@ -174,8 +175,11 @@ class HealthIcon extends FlxSprite
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.isPixel = data.isPixel ?? false;
|
|
||||||
this.characterId = data.id;
|
this.characterId = data.id;
|
||||||
|
this.isPixel = data.isPixel ?? false;
|
||||||
|
|
||||||
|
loadCharacter(characterId);
|
||||||
|
|
||||||
this.size.set(data.scale ?? 1.0, data.scale ?? 1.0);
|
this.size.set(data.scale ?? 1.0, data.scale ?? 1.0);
|
||||||
this.offset.x = (data.offsets != null) ? data.offsets[0] : 0.0;
|
this.offset.x = (data.offsets != null) ? data.offsets[0] : 0.0;
|
||||||
this.offset.y = (data.offsets != null) ? data.offsets[1] : 0.0;
|
this.offset.y = (data.offsets != null) ? data.offsets[1] : 0.0;
|
||||||
|
|
|
@ -698,7 +698,15 @@ class PlayState extends MusicBeatSubState
|
||||||
FlxG.sound.music.pause();
|
FlxG.sound.music.pause();
|
||||||
FlxG.sound.music.time = (startTimestamp);
|
FlxG.sound.music.time = (startTimestamp);
|
||||||
|
|
||||||
vocals = currentChart.buildVocals();
|
if (!overrideMusic)
|
||||||
|
{
|
||||||
|
vocals = currentChart.buildVocals();
|
||||||
|
|
||||||
|
if (vocals.members.length == 0)
|
||||||
|
{
|
||||||
|
trace('WARNING: No vocals found for this song.');
|
||||||
|
}
|
||||||
|
}
|
||||||
vocals.pause();
|
vocals.pause();
|
||||||
vocals.time = 0;
|
vocals.time = 0;
|
||||||
|
|
||||||
|
|
|
@ -154,6 +154,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
{
|
{
|
||||||
if (metadata == null || metadata.playData == null) continue;
|
if (metadata == null || metadata.playData == null) continue;
|
||||||
|
|
||||||
|
// If there are no difficulties in the metadata, there's a problem.
|
||||||
|
if (metadata.playData.difficulties.length == 0)
|
||||||
|
{
|
||||||
|
throw 'Song $id has no difficulties listed in metadata!';
|
||||||
|
}
|
||||||
|
|
||||||
// There may be more difficulties in the chart file than in the metadata,
|
// There may be more difficulties in the chart file than in the metadata,
|
||||||
// (i.e. non-playable charts like the one used for Pico on the speaker in Stress)
|
// (i.e. non-playable charts like the one used for Pico on the speaker in Stress)
|
||||||
// but all the difficulties in the metadata must be in the chart file.
|
// but all the difficulties in the metadata must be in the chart file.
|
||||||
|
|
|
@ -421,6 +421,8 @@ typedef RawSaveData =
|
||||||
/**
|
/**
|
||||||
* A semantic versioning string for the save data format.
|
* A semantic versioning string for the save data format.
|
||||||
*/
|
*/
|
||||||
|
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||||
|
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||||
var version:Version;
|
var version:Version;
|
||||||
|
|
||||||
var api:SaveApiData;
|
var api:SaveApiData;
|
||||||
|
|
|
@ -404,7 +404,6 @@ class ChartEditorDialogHandler
|
||||||
{
|
{
|
||||||
if (ChartEditorAudioHandler.loadInstFromBytes(state, selectedFile.bytes, instId))
|
if (ChartEditorAudioHandler.loadInstFromBytes(state, selectedFile.bytes, instId))
|
||||||
{
|
{
|
||||||
trace('Selected file: ' + selectedFile.fullPath);
|
|
||||||
#if !mac
|
#if !mac
|
||||||
NotificationManager.instance.addNotification(
|
NotificationManager.instance.addNotification(
|
||||||
{
|
{
|
||||||
|
@ -415,13 +414,12 @@ class ChartEditorDialogHandler
|
||||||
});
|
});
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
state.switchToCurrentInstrumental();
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
removeDropHandler(onDropFile);
|
removeDropHandler(onDropFile);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('Failed to load instrumental (${selectedFile.fullPath})');
|
|
||||||
|
|
||||||
#if !mac
|
#if !mac
|
||||||
NotificationManager.instance.addNotification(
|
NotificationManager.instance.addNotification(
|
||||||
{
|
{
|
||||||
|
@ -452,6 +450,7 @@ class ChartEditorDialogHandler
|
||||||
});
|
});
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
state.switchToCurrentInstrumental();
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
removeDropHandler(onDropFile);
|
removeDropHandler(onDropFile);
|
||||||
}
|
}
|
||||||
|
@ -570,6 +569,12 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var newSongMetadata:SongMetadata = new SongMetadata('', '', 'default');
|
var newSongMetadata:SongMetadata = new SongMetadata('', '', 'default');
|
||||||
|
|
||||||
|
newSongMetadata.playData.difficulties = switch (targetVariation)
|
||||||
|
{
|
||||||
|
case 'erect': ['erect', 'nightmare'];
|
||||||
|
default: ['easy', 'normal', 'hard'];
|
||||||
|
};
|
||||||
|
|
||||||
var inputSongName:Null<TextField> = dialog.findComponent('inputSongName', TextField);
|
var inputSongName:Null<TextField> = dialog.findComponent('inputSongName', TextField);
|
||||||
if (inputSongName == null) throw 'Could not locate inputSongName TextField in Song Metadata dialog';
|
if (inputSongName == null) throw 'Could not locate inputSongName TextField in Song Metadata dialog';
|
||||||
inputSongName.onChange = function(event:UIEvent) {
|
inputSongName.onChange = function(event:UIEvent) {
|
||||||
|
|
|
@ -554,6 +554,9 @@ class ChartEditorState extends HaxeUIState
|
||||||
notePreviewDirty = true;
|
notePreviewDirty = true;
|
||||||
notePreviewViewportBoundsDirty = true;
|
notePreviewViewportBoundsDirty = true;
|
||||||
|
|
||||||
|
// Make sure the difficulty we selected is in the list of difficulties.
|
||||||
|
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
|
||||||
|
|
||||||
return selectedDifficulty;
|
return selectedDifficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -971,6 +974,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
result = [];
|
result = [];
|
||||||
trace('Initializing blank note data for difficulty ' + selectedDifficulty);
|
trace('Initializing blank note data for difficulty ' + selectedDifficulty);
|
||||||
currentSongChartData.notes.set(selectedDifficulty, result);
|
currentSongChartData.notes.set(selectedDifficulty, result);
|
||||||
|
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -979,6 +983,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
function set_currentSongChartNoteData(value:Array<SongNoteData>):Array<SongNoteData>
|
function set_currentSongChartNoteData(value:Array<SongNoteData>):Array<SongNoteData>
|
||||||
{
|
{
|
||||||
currentSongChartData.notes.set(selectedDifficulty, value);
|
currentSongChartData.notes.set(selectedDifficulty, value);
|
||||||
|
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4089,7 +4094,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
}
|
}
|
||||||
|
|
||||||
subStateClosed.add(fixCamera);
|
subStateClosed.add(fixCamera);
|
||||||
subStateClosed.add(updateConductor);
|
subStateClosed.add(resetConductorAfterTest);
|
||||||
|
|
||||||
FlxTransitionableState.skipNextTransIn = false;
|
FlxTransitionableState.skipNextTransIn = false;
|
||||||
FlxTransitionableState.skipNextTransOut = false;
|
FlxTransitionableState.skipNextTransOut = false;
|
||||||
|
@ -4122,10 +4127,9 @@ class ChartEditorState extends HaxeUIState
|
||||||
add(this.component);
|
add(this.component);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateConductor(_:FlxSubState = null):Void
|
function resetConductorAfterTest(_:FlxSubState = null):Void
|
||||||
{
|
{
|
||||||
var targetPos = scrollPositionInMs;
|
moveSongToScrollPosition();
|
||||||
Conductor.update(targetPos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function postLoadInstrumental():Void
|
public function postLoadInstrumental():Void
|
||||||
|
@ -4179,12 +4183,14 @@ class ChartEditorState extends HaxeUIState
|
||||||
function moveSongToScrollPosition():Void
|
function moveSongToScrollPosition():Void
|
||||||
{
|
{
|
||||||
// Update the songPosition in the audio tracks.
|
// Update the songPosition in the audio tracks.
|
||||||
if (audioInstTrack != null) audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;
|
if (audioInstTrack != null)
|
||||||
|
{
|
||||||
|
audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;
|
||||||
|
// Update the songPosition in the Conductor.
|
||||||
|
Conductor.update(audioInstTrack.time);
|
||||||
|
}
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = scrollPositionInMs + playheadPositionInMs;
|
if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = scrollPositionInMs + playheadPositionInMs;
|
||||||
|
|
||||||
// Update the songPosition in the Conductor.
|
|
||||||
Conductor.update(audioInstTrack.time);
|
|
||||||
|
|
||||||
// We need to update the note sprites because we changed the scroll position.
|
// We need to update the note sprites because we changed the scroll position.
|
||||||
noteDisplayDirty = true;
|
noteDisplayDirty = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,19 @@ class ArrayTools
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push an element to the array if it is not already present.
|
||||||
|
* @param input The array to push to
|
||||||
|
* @param element The element to push
|
||||||
|
* @return Whether the element was pushed
|
||||||
|
*/
|
||||||
|
public static function pushUnique<T>(input:Array<T>, element:T):Bool
|
||||||
|
{
|
||||||
|
if (input.contains(element)) return false;
|
||||||
|
input.push(element);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all elements from the array, without creating a new array.
|
* Remove all elements from the array, without creating a new array.
|
||||||
* @param array The array to clear.
|
* @param array The array to clear.
|
||||||
|
|
Loading…
Reference in a new issue