ng release and blank controls

This commit is contained in:
Brandon 2020-11-01 14:16:22 -05:00
parent 968a001a36
commit 5fa4bc458e
65 changed files with 3468 additions and 126 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
export/
.vscode/
.vscode/
APIStuff.hx

View File

@ -1,14 +1,16 @@
# Changelog
All notable changes will be documented in this file.
## [Unreleased]
## [1.1.0]
### Added
- 32bit support
- Controller (dancepads) support
- Pause screen
- Main Menu overhaul
- Cool intro screen thing
- Uh lots of bullshit
- Remind me to actually add this later lmaooo
## [0.1.0] - 2020-10-05
## [1.0.0] - 2020-10-05
### Added
- Uh, everything. This the game's initial gamejam release. We put it out

View File

@ -97,5 +97,6 @@
<!--Place custom nodes like icons here (higher priority to override the HaxeFlixel icon)-->
<icon path="art/icon.png" />
<haxedef name="SKIP_TO_PLAYSTATE" if="debug" />
<!-- <haxedef name="SKIP_TO_PLAYSTATE" if="debug" /> -->
<haxedef name="NG_LOGIN" />
</project>

Binary file not shown.

BIN
art/Start_Screen_Assets.fla Normal file

Binary file not shown.

View File

@ -0,0 +1,11 @@
@echo off
color 0a
cd ..
@echo on
echo BUILDING GAME
lime build html5 -release
echo UPLOADING TO ITCH
butler push ./export/release/html5/bin ninja-muffin24/funkin:html5
butler status ninja-muffin24/funkin:html5
echo ITCH SHIT UPDATED LMAOOOOO
pause

View File

@ -3,12 +3,12 @@ color 0a
cd ..
@echo on
echo BUILDING GAME
lime build hl -debug
lime build windows -release
echo UPLOADING 64 BIT VERSION TO ITCH
butler push ./export/debug/hl/bin ninja-muffin24/funkin:windows-64bit
lime build hl -debug -32
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-64bit
lime build windows -release -32
echo UPLOADING 32 BIT VERSION TO ITCH
butler push ./export/debug/hl/bin ninja-muffin24/funkin:windows-32bit
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-32bit
butler status ninja-muffin24/funkin:windows-32bit
butler status ninja-muffin24/funkin:windows-64bit
echo ITCH SHIT UPDATED LMAOOOOO

BIN
art/thumbnailNewer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

BIN
assets/images/backspace.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="backspace.png">
<!-- Created with Adobe Animate version 20.0.0.17400 -->
<!-- http://www.adobe.com/products/animate.html -->
<SubTexture name="backspace PRESSED0000" x="0" y="0" width="288" height="160" frameX="-14" frameY="-6" frameWidth="314" frameHeight="174"/>
<SubTexture name="backspace PRESSED0001" x="0" y="0" width="288" height="160" frameX="-14" frameY="-6" frameWidth="314" frameHeight="174"/>
<SubTexture name="backspace PRESSED0002" x="298" y="0" width="314" height="174"/>
<SubTexture name="backspace to exit0000" x="622" y="0" width="280" height="106"/>
<SubTexture name="backspace to exit white0000" x="0" y="184" width="280" height="106"/>
</TextureAtlas>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="gfDanceTitle.png">
<!-- Created with Adobe Animate version 20.0.0.17400 -->
<!-- http://www.adobe.com/products/animate.html -->
<SubTexture name="gfDance0000" x="0" y="0" width="717" height="648" frameX="-2" frameY="-14" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0001" x="727" y="0" width="721" height="648" frameX="0" frameY="-14" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0002" x="1458" y="0" width="721" height="646" frameX="0" frameY="-16" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0003" x="2189" y="0" width="717" height="646" frameX="-2" frameY="-16" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0004" x="2916" y="0" width="717" height="649" frameX="-2" frameY="-13" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0005" x="0" y="659" width="717" height="649" frameX="-2" frameY="-13" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0006" x="727" y="659" width="717" height="650" frameX="-2" frameY="-12" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0007" x="1454" y="659" width="717" height="661" frameX="-2" frameY="-1" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0008" x="1454" y="659" width="717" height="661" frameX="-2" frameY="-1" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0009" x="1454" y="659" width="717" height="661" frameX="-2" frameY="-1" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0010" x="2181" y="659" width="717" height="662" frameX="-2" frameY="0" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0011" x="2181" y="659" width="717" height="662" frameX="-2" frameY="0" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0012" x="2181" y="659" width="717" height="662" frameX="-2" frameY="0" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0013" x="2908" y="659" width="717" height="661" frameX="-2" frameY="-1" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0014" x="2908" y="659" width="717" height="661" frameX="-2" frameY="-1" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0015" x="0" y="1331" width="717" height="650" frameX="-2" frameY="-12" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0016" x="727" y="1331" width="721" height="650" frameX="0" frameY="-12" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0017" x="1458" y="1331" width="721" height="650" frameX="0" frameY="-12" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0018" x="2189" y="1331" width="717" height="650" frameX="-2" frameY="-12" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0019" x="2916" y="1331" width="717" height="651" frameX="-2" frameY="-11" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0020" x="0" y="1992" width="717" height="651" frameX="-2" frameY="-11" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0021" x="727" y="1992" width="717" height="652" frameX="-2" frameY="-10" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0022" x="1454" y="1992" width="717" height="657" frameX="-2" frameY="-5" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0023" x="1454" y="1992" width="717" height="657" frameX="-2" frameY="-5" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0024" x="1454" y="1992" width="717" height="657" frameX="-2" frameY="-5" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0025" x="2181" y="1992" width="717" height="656" frameX="-2" frameY="-6" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0026" x="2181" y="1992" width="717" height="656" frameX="-2" frameY="-6" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0027" x="2181" y="1992" width="717" height="656" frameX="-2" frameY="-6" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0028" x="2908" y="1992" width="717" height="656" frameX="-2" frameY="-6" frameWidth="721" frameHeight="662"/>
<SubTexture name="gfDance0029" x="2908" y="1992" width="717" height="656" frameX="-2" frameY="-6" frameWidth="721" frameHeight="662"/>
</TextureAtlas>

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="logoBumpin.png">
<!-- Created with Adobe Animate version 20.0.0.17400 -->
<!-- http://www.adobe.com/products/animate.html -->
<SubTexture name="logo bumpin0000" x="0" y="0" width="894" height="670" frameX="-22" frameY="-16" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0001" x="904" y="0" width="939" height="703"/>
<SubTexture name="logo bumpin0002" x="904" y="0" width="939" height="703"/>
<SubTexture name="logo bumpin0003" x="0" y="713" width="911" height="683" frameX="-14" frameY="-10" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0004" x="0" y="713" width="911" height="683" frameX="-14" frameY="-10" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0005" x="921" y="713" width="905" height="679" frameX="-17" frameY="-12" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0006" x="921" y="713" width="905" height="679" frameX="-17" frameY="-12" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0007" x="921" y="713" width="905" height="679" frameX="-17" frameY="-12" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0008" x="921" y="713" width="905" height="679" frameX="-17" frameY="-12" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0009" x="921" y="713" width="905" height="679" frameX="-17" frameY="-12" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0010" x="921" y="713" width="905" height="679" frameX="-17" frameY="-12" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0011" x="921" y="713" width="905" height="679" frameX="-17" frameY="-12" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0012" x="921" y="713" width="905" height="679" frameX="-17" frameY="-12" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0013" x="921" y="713" width="905" height="679" frameX="-17" frameY="-12" frameWidth="939" frameHeight="703"/>
<SubTexture name="logo bumpin0014" x="921" y="713" width="905" height="679" frameX="-17" frameY="-12" frameWidth="939" frameHeight="703"/>
</TextureAtlas>

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="titleEnter.png">
<!-- Created with Adobe Animate version 20.0.0.17400 -->
<!-- http://www.adobe.com/products/animate.html -->
<SubTexture name="ENTER PRESSED0000" x="0" y="0" width="1477" height="79"/>
<SubTexture name="ENTER PRESSED0001" x="1487" y="0" width="1477" height="79"/>
<SubTexture name="ENTER PRESSED0002" x="0" y="0" width="1477" height="79"/>
<SubTexture name="ENTER PRESSED0003" x="1487" y="0" width="1477" height="79"/>
<SubTexture name="ENTER PRESSED0004" x="0" y="0" width="1477" height="79"/>
<SubTexture name="ENTER PRESSED0005" x="1487" y="0" width="1477" height="79"/>
<SubTexture name="ENTER PRESSED0006" x="1487" y="0" width="1477" height="79"/>
<SubTexture name="ENTER PRESSED0007" x="0" y="0" width="1477" height="79"/>
<SubTexture name="ENTER PRESSED0008" x="1487" y="0" width="1477" height="79"/>
<SubTexture name="Press Enter to Begin0000" x="0" y="89" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0001" x="1505" y="89" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0002" x="0" y="195" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0003" x="1505" y="195" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0004" x="0" y="301" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0005" x="1505" y="301" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0006" x="0" y="407" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0007" x="1505" y="407" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0008" x="0" y="513" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0009" x="1505" y="513" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0010" x="0" y="619" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0011" x="1505" y="619" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0012" x="0" y="725" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0013" x="1505" y="725" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0014" x="0" y="831" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0015" x="1505" y="831" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0016" x="0" y="937" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0017" x="1505" y="937" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0018" x="0" y="1043" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0019" x="1505" y="1043" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0020" x="0" y="1149" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0021" x="1505" y="1149" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0022" x="0" y="1255" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0023" x="0" y="1255" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0024" x="1505" y="1255" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0025" x="0" y="1149" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0026" x="0" y="1361" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0027" x="1505" y="1361" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0028" x="0" y="1467" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0029" x="1505" y="1467" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0030" x="0" y="1573" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0031" x="1505" y="1573" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0032" x="0" y="1679" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0033" x="1505" y="1679" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0034" x="0" y="1785" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0035" x="1505" y="1785" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0036" x="0" y="1891" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0037" x="1505" y="1891" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0038" x="0" y="1997" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0039" x="1505" y="1997" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0040" x="0" y="2103" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0041" x="1505" y="2103" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0042" x="0" y="2209" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0043" x="1505" y="2209" width="1495" height="96"/>
<SubTexture name="Press Enter to Begin0044" x="0" y="89" width="1495" height="96"/>
</TextureAtlas>

View File

@ -17,6 +17,10 @@ class Alphabet extends FlxSpriteGroup
public var delay:Float = 0.05;
public var paused:Bool = false;
// for menu shit
public var targetY:Float = 0;
public var isMenuItem:Bool = false;
public var text:String = "";
var _finalText:String = "";
@ -207,6 +211,14 @@ class Alphabet extends FlxSpriteGroup
override function update(elapsed:Float)
{
if (isMenuItem)
{
var scaledY = FlxMath.remapToRange(targetY, 0, 1, 0, 1.3);
y = FlxMath.lerp(y, (scaledY * 120) + (FlxG.height * 0.48), 0.16);
x = FlxMath.lerp(x, (targetY * 20) + 90, 0.16);
}
super.update(elapsed);
}
}

View File

@ -3,47 +3,59 @@ package;
import flixel.FlxG;
import flixel.FlxSprite;
import flixel.addons.display.FlxGridOverlay;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.text.FlxText;
#if switch
import openfl.events.GameInputEvent;
import openfl.ui.GameInput;
import openfl.ui.GameInputDevice;
import openfl.ui.GameInputControl;
#end
class FreeplayState extends MusicBeatState
{
var songs:Array<String> = ["Bopeebo", "Dadbattle", "Fresh", "Tutorial", "Spookeez", "South", "Monster"];
var songs:Array<String> = ["Bopeebo", "Dadbattle", "Fresh", "Tutorial"];
var selector:FlxText;
var curSelected:Int = 0;
private var grpSongs:FlxTypedGroup<Alphabet>;
override function create()
{
if (!FlxG.sound.music.playing)
FlxG.sound.playMusic('assets/music/freakyMenu' + TitleState.soundExt);
if (StoryMenuState.weekUnlocked[1])
{
songs.push('Spookeez');
songs.push('South');
}
// LOAD MUSIC
// LOAD CHARACTERS
var bg:FlxSprite = FlxGridOverlay.create(20, 20);
var bg:FlxSprite = new FlxSprite().loadGraphic(AssetPaths.menuBGBlue__png);
add(bg);
grpSongs = new FlxTypedGroup<Alphabet>();
add(grpSongs);
for (i in 0...songs.length)
{
var songText:Alphabet = new Alphabet(0, (70 * i) + 30, songs[i], true, false);
add(songText);
songText.x += 40;
songText.isMenuItem = true;
songText.targetY = i;
grpSongs.add(songText);
// songText.x += 40;
// DONT PUT X IN THE FIRST PARAMETER OF new ALPHABET() !!
// songText.screenCenter(X);
}
FlxG.sound.playMusic('assets/music/title' + TitleState.soundExt, 0);
FlxG.sound.music.fadeIn(2, 0, 0.8);
changeSelection();
// FlxG.sound.playMusic('assets/music/title' + TitleState.soundExt, 0);
// FlxG.sound.music.fadeIn(2, 0, 0.8);
selector = new FlxText();
selector.size = 40;
selector.text = ">";
add(selector);
// add(selector);
var swag:Alphabet = new Alphabet(1, 0, "swag");
@ -59,47 +71,53 @@ class FreeplayState extends MusicBeatState
if (upP)
{
curSelected -= 1;
changeSelection(-1);
}
if (downP)
{
curSelected += 1;
changeSelection(1);
}
#if switch
if (gamepad.anyJustPressed(["UP", "DPAD_UP", "LEFT_STICK_DIGITAL_UP"]))
{
curSelected -= 1;
}
if (gamepad.anyJustPressed(["DOWN", "DPAD_DOWN", "LEFT_STICK_DIGITAL_DOWN"]))
{
curSelected += 1;
}
#end
if (controls.BACK)
{
FlxG.switchState(new MainMenuState());
}
if (accepted)
{
PlayState.SONG = Song.loadFromJson(songs[curSelected].toLowerCase(), songs[curSelected].toLowerCase());
PlayState.isStoryMode = false;
FlxG.switchState(new PlayState());
FlxG.sound.music.stop();
}
}
function changeSelection(change:Int = 0)
{
curSelected += change;
if (curSelected < 0)
curSelected = songs.length - 1;
if (curSelected >= songs.length)
curSelected = 0;
selector.y = (70 * curSelected) + 30;
// selector.y = (70 * curSelected) + 30;
if (accepted)
var bullShit:Int = 0;
for (item in grpSongs.members)
{
PlayState.SONG = Song.loadFromJson(songs[curSelected].toLowerCase());
PlayState.isStoryMode = false;
FlxG.switchState(new PlayState());
FlxG.sound.music.stop();
}
item.targetY = bullShit - curSelected;
bullShit++;
#if switch
if (gamepad.anyJustPressed(["B"])) //"B" is swapped with "A" on Switch
item.alpha = 0.6;
// item.setGraphicSize(Std.int(item.width * 0.8));
if (item.targetY == 0)
{
PlayState.SONG = Song.loadFromJson(songs[curSelected].toLowerCase());
PlayState.isStoryMode = false;
FlxG.switchState(new PlayState());
FlxG.sound.music.stop();
item.alpha = 1;
// item.setGraphicSize(Std.int(item.width));
}
#end
}
}
}

View File

@ -12,6 +12,8 @@ class GameOverSubstate extends MusicBeatSubstate
var bf:Boyfriend;
var camFollow:FlxObject;
// var
public function new(x:Float, y:Float)
{
super();
@ -39,11 +41,21 @@ class GameOverSubstate extends MusicBeatSubstate
{
super.update(elapsed);
if (FlxG.keys.justPressed.ENTER)
if (controls.ACCEPT)
{
endBullshit();
}
if (controls.BACK)
{
FlxG.sound.music.stop();
if (PlayState.isStoryMode)
FlxG.switchState(new StoryMenuState());
else
FlxG.switchState(new FreeplayState());
}
if (bf.animation.curAnim.name == 'firstDeath' && bf.animation.curAnim.curFrame == 12)
{
FlxG.camera.follow(camFollow, LOCKON, 0.01);

View File

@ -73,70 +73,76 @@ class MainMenuState extends MusicBeatState
super.create();
}
var selectedSomethin:Bool = false;
override function update(elapsed:Float)
{
if (controls.UP_P)
if (!selectedSomethin)
{
FlxG.sound.play('assets/sounds/scrollMenu' + TitleState.soundExt);
changeItem(-1);
}
if (controls.UP_P)
{
FlxG.sound.play('assets/sounds/scrollMenu' + TitleState.soundExt);
changeItem(-1);
}
if (controls.DOWN_P)
{
FlxG.sound.play('assets/sounds/scrollMenu' + TitleState.soundExt);
changeItem(1);
}
if (controls.DOWN_P)
{
FlxG.sound.play('assets/sounds/scrollMenu' + TitleState.soundExt);
changeItem(1);
}
if (controls.BACK)
{
FlxG.switchState(new TitleState());
if (controls.BACK)
{
FlxG.switchState(new TitleState());
}
if (controls.ACCEPT)
{
if (optionShit[curSelected] == 'donate')
{
FlxG.openURL('https://ninja-muffin24.itch.io/funkin');
}
else
{
selectedSomethin = true;
FlxG.sound.play('assets/sounds/confirmMenu' + TitleState.soundExt);
FlxFlicker.flicker(magenta, 1.1, 0.15, false);
menuItems.forEach(function(spr:FlxSprite)
{
if (curSelected != spr.ID)
{
FlxTween.tween(spr, {alpha: 0}, 0.4, {
ease: FlxEase.quadOut,
onComplete: function(twn:FlxTween)
{
spr.kill();
}
});
}
else
{
FlxFlicker.flicker(spr, 1, 0.06, false, false, function(flick:FlxFlicker)
{
var daChoice:String = optionShit[curSelected];
switch (daChoice)
{
case 'story mode':
FlxG.switchState(new StoryMenuState());
case 'freeplay':
FlxG.switchState(new FreeplayState());
}
});
}
});
}
}
}
super.update(elapsed);
if (controls.ACCEPT)
{
if (optionShit[curSelected] == 'donate')
{
FlxG.openURL('https://ninja-muffin24.itch.io/funkin');
}
else
{
FlxG.sound.play('assets/sounds/confirmMenu' + TitleState.soundExt);
FlxFlicker.flicker(magenta, 1.1, 0.15, false);
menuItems.forEach(function(spr:FlxSprite)
{
if (curSelected != spr.ID)
{
FlxTween.tween(spr, {alpha: 0}, 0.4, {
ease: FlxEase.quadOut,
onComplete: function(twn:FlxTween)
{
spr.kill();
}
});
}
else
{
FlxFlicker.flicker(spr, 1, 0.06, false, false, function(flick:FlxFlicker)
{
var daChoice:String = optionShit[curSelected];
switch (daChoice)
{
case 'story mode':
FlxG.switchState(new StoryMenuState());
case 'freeplay':
FlxG.switchState(new FreeplayState());
}
});
}
});
}
}
menuItems.forEach(function(spr:FlxSprite)
{
spr.screenCenter(X);

158
source/NGio.hx Normal file
View File

@ -0,0 +1,158 @@
package;
import flixel.FlxG;
import flixel.util.FlxSignal;
import io.newgrounds.NG;
import io.newgrounds.components.ScoreBoardComponent.Period;
import io.newgrounds.objects.Medal;
import io.newgrounds.objects.Score;
import io.newgrounds.objects.ScoreBoard;
import openfl.display.Stage;
/**
* MADE BY GEOKURELI THE LEGENED GOD HERO MVP
*/
class NGio
{
public static var isLoggedIn:Bool = false;
public static var scoreboardsLoaded:Bool = false;
public static var scoreboardArray:Array<Score> = [];
public static var ngDataLoaded(default, null):FlxSignal = new FlxSignal();
public static var ngScoresLoaded(default, null):FlxSignal = new FlxSignal();
public function new(api:String, encKey:String, ?sessionId:String)
{
trace("connecting to newgrounds");
NG.createAndCheckSession(api, sessionId);
NG.core.verbose = true;
// Set the encryption cipher/format to RC4/Base64. AES128 and Hex are not implemented yet
NG.core.initEncryption(encKey); // Found in you NG project view
trace(NG.core.attemptingLogin);
if (NG.core.attemptingLogin)
{
/* a session_id was found in the loadervars, this means the user is playing on newgrounds.com
* and we should login shortly. lets wait for that to happen
*/
trace("attempting login");
NG.core.onLogin.add(onNGLogin);
}
else
{
/* They are NOT playing on newgrounds.com, no session id was found. We must start one manually, if we want to.
* Note: This will cause a new browser window to pop up where they can log in to newgrounds
*/
NG.core.requestLogin(onNGLogin);
}
}
function onNGLogin():Void
{
trace('logged in! user:${NG.core.user.name}');
isLoggedIn = true;
FlxG.save.data.sessionId = NG.core.sessionId;
// FlxG.save.flush();
// Load medals then call onNGMedalFetch()
NG.core.requestMedals(onNGMedalFetch);
// Load Scoreboards hten call onNGBoardsFetch()
NG.core.requestScoreBoards(onNGBoardsFetch);
ngDataLoaded.dispatch();
}
// --- MEDALS
function onNGMedalFetch():Void
{
/*
// Reading medal info
for (id in NG.core.medals.keys())
{
var medal = NG.core.medals.get(id);
trace('loaded medal id:$id, name:${medal.name}, description:${medal.description}');
}
// Unlocking medals
var unlockingMedal = NG.core.medals.get(54352);// medal ids are listed in your NG project viewer
if (!unlockingMedal.unlocked)
unlockingMedal.sendUnlock();
*/
}
// --- SCOREBOARDS
function onNGBoardsFetch():Void
{
/*
// Reading medal info
for (id in NG.core.scoreBoards.keys())
{
var board = NG.core.scoreBoards.get(id);
trace('loaded scoreboard id:$id, name:${board.name}');
}
*/
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
// Posting a score thats OVER 9000!
// board.postScore(FlxG.random.int(0, 1000));
// --- To view the scores you first need to select the range of scores you want to see ---
// add an update listener so we know when we get the new scores
// board.onUpdate.add(onNGScoresFetch);
trace("shoulda got score by NOW!");
// board.requestScores(20);// get the best 10 scores ever logged
// more info on scores --- http://www.newgrounds.io/help/components/#scoreboard-getscores
}
inline static public function postScore(score:Int = 0, song:String)
{
if (isLoggedIn)
{
for (id in NG.core.scoreBoards.keys())
{
var board = NG.core.scoreBoards.get(id);
if (song == board.name)
{
board.postScore(score, "Uhh meow?");
}
// trace('loaded scoreboard id:$id, name:${board.name}');
}
}
}
function onNGScoresFetch():Void
{
scoreboardsLoaded = true;
ngScoresLoaded.dispatch();
/*
for (score in NG.core.scoreBoards.get(8737).scores)
{
trace('score loaded user:${score.user.name}, score:${score.formatted_value}');
}
*/
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
// board.postScore(HighScore.score);
// NGio.scoreboardArray = NG.core.scoreBoards.get(8004).scores;
}
inline static public function unlockMedal(id:Int)
{
if (isLoggedIn)
{
var medal = NG.core.medals.get(id);
if (!medal.unlocked)
medal.sendUnlock();
}
}
}

View File

@ -80,6 +80,7 @@ class PlayState extends MusicBeatState
var halloweenBG:FlxSprite;
var talking:Bool = true;
var songScore:Int = 0;
override public function create()
{
@ -593,7 +594,7 @@ class PlayState extends MusicBeatState
if (FlxG.keys.justPressed.ESCAPE)
{
FlxG.switchState(new ChartingState());
// FlxG.switchState(new ChartingState());
}
// FlxG.watch.addQuick('VOL', vocals.amplitudeLeft);
@ -602,6 +603,9 @@ class PlayState extends MusicBeatState
healthHeads.setGraphicSize(Std.int(FlxMath.lerp(100, healthHeads.width, 0.98)));
healthHeads.x = healthBar.x + (healthBar.width * (FlxMath.remapToRange(healthBar.percent, 0, 100, 100, 0) * 0.01)) - (healthHeads.width / 2);
if (health > 2)
health = 2;
if (healthBar.percent < 20)
healthHeads.animation.play('unhealthy');
else
@ -609,8 +613,8 @@ class PlayState extends MusicBeatState
/* if (FlxG.keys.justPressed.NINE)
FlxG.switchState(new Charting()); */
if (FlxG.keys.justPressed.EIGHT)
FlxG.switchState(new AnimationDebug(SONG.player2));
// if (FlxG.keys.justPressed.EIGHT)
// FlxG.switchState(new AnimationDebug(SONG.player2));
if (startingSong)
{
@ -805,15 +809,24 @@ class PlayState extends MusicBeatState
{
trace('SONG DONE' + isStoryMode);
NGio.postScore(songScore, SONG.song);
if (isStoryMode)
{
storyPlaylist.remove(storyPlaylist[0]);
if (storyPlaylist.length <= 0)
{
FlxG.switchState(new TitleState());
FlxG.sound.playMusic('assets/music/freakyMenu' + TitleState.soundExt);
FlxG.switchState(new StoryMenuState());
StoryMenuState.weekUnlocked[1] = true;
NGio.unlockMedal(60961);
FlxG.save.data.weekUnlocked = StoryMenuState.weekUnlocked;
FlxG.save.flush();
}
else
{
@ -851,22 +864,28 @@ class PlayState extends MusicBeatState
//
var rating:FlxSprite = new FlxSprite();
var score:Int = 350;
var daRating:String = "sick";
if (noteDiff > Conductor.safeZoneOffset * 0.9)
{
daRating = 'shit';
score = 50;
}
else if (noteDiff > Conductor.safeZoneOffset * 0.75)
{
daRating = 'bad';
score = 100;
}
else if (noteDiff > Conductor.safeZoneOffset * 0.2)
{
daRating = 'good';
score = 200;
}
songScore += score;
/* if (combo > 60)
daRating = 'sick';
else if (combo > 12)
@ -916,7 +935,9 @@ class PlayState extends MusicBeatState
numScore.acceleration.y = FlxG.random.int(200, 300);
numScore.velocity.y -= FlxG.random.int(140, 160);
numScore.velocity.x = FlxG.random.float(-5, 5);
add(numScore);
if (combo >= 10 || combo == 0)
add(numScore);
FlxTween.tween(numScore, {alpha: 0}, 0.2, {
onComplete: function(tween:FlxTween)
@ -1102,6 +1123,8 @@ class PlayState extends MusicBeatState
}
combo = 0;
songScore -= 10;
FlxG.sound.play('assets/sounds/missnote' + FlxG.random.int(1, 3) + TitleState.soundExt, FlxG.random.float(0.1, 0.2));
// FlxG.sound.play('assets/sounds/missnote1' + TitleState.soundExt, 1, false);
// FlxG.log.add('played imss note');
@ -1283,7 +1306,7 @@ class PlayState extends MusicBeatState
if (!boyfriend.animation.curAnim.name.startsWith("sing"))
boyfriend.playAnim('idle');
if (totalBeats % 8 == 6)
if (totalBeats % 8 == 7 && curSong == 'Bopeebo')
{
boyfriend.playAnim('hey', true);

View File

@ -16,7 +16,7 @@ class StoryMenuState extends MusicBeatState
{
var scoreText:FlxText;
var weekData:Array<Dynamic> = [['Tutorial', 'Bopeebo', 'Fresh', 'Dadbattle'], ['Spookeez', 'South', 'Monster']];
var weekData:Array<Dynamic> = [['Tutorial', 'Bopeebo', 'Fresh', 'Dadbattle'], ['Spookeez', 'South']];
var curDifficulty:Int = 1;
public static var weekUnlocked:Array<Bool> = [true, false];
@ -38,6 +38,9 @@ class StoryMenuState extends MusicBeatState
override function create()
{
if (!FlxG.sound.music.playing)
FlxG.sound.playMusic('assets/music/freakyMenu' + TitleState.soundExt);
persistentUpdate = persistentDraw = true;
scoreText = new FlxText(10, 10, 0, "SCORE: 49324858", 36);
@ -102,6 +105,7 @@ class StoryMenuState extends MusicBeatState
case 'bf':
weekCharacterThing.setGraphicSize(Std.int(weekCharacterThing.width * 0.9));
weekCharacterThing.updateHitbox();
weekCharacterThing.x -= 80;
case 'gf':
weekCharacterThing.setGraphicSize(Std.int(weekCharacterThing.width * 0.5));
weekCharacterThing.updateHitbox();
@ -145,8 +149,8 @@ class StoryMenuState extends MusicBeatState
txtTracklist.font = rankText.font;
txtTracklist.color = 0xFFe55777;
add(txtTracklist);
add(rankText);
add(scoreText);
// add(rankText);
// add(scoreText);
updateText();
@ -243,7 +247,8 @@ class StoryMenuState extends MusicBeatState
PlayState.SONG = Song.loadFromJson(PlayState.storyPlaylist[0].toLowerCase() + diffic, PlayState.storyPlaylist[0].toLowerCase());
new FlxTimer().start(1, function(tmr:FlxTimer)
{
FlxG.sound.music.stop();
if (FlxG.sound.music != null)
FlxG.sound.music.stop();
FlxG.switchState(new PlayState());
});
}
@ -274,9 +279,11 @@ class StoryMenuState extends MusicBeatState
}
sprDifficulty.alpha = 0;
sprDifficulty.y -= 15;
FlxTween.tween(sprDifficulty, {y: sprDifficulty.y + 15, alpha: 1}, 0.07);
// USING THESE WEIRD VALUES SO THAT IT DOESNT FLOAT UP
sprDifficulty.y = leftArrow.y - 15;
FlxTween.tween(sprDifficulty, {y: leftArrow.y + 15, alpha: 1}, 0.07);
}
function changeWeek(change:Int = 0):Void

View File

@ -8,6 +8,7 @@ import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.transition.TransitionData;
import flixel.graphics.FlxGraphic;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxGroup;
import flixel.input.gamepad.FlxGamepad;
import flixel.math.FlxPoint;
@ -53,6 +54,10 @@ class TitleState extends MusicBeatState
super.create();
#if (!debug && NG_LOGIN)
var ng:NGio = new NGio(APIStuff.API, APIStuff.EncKey);
#end
#if SKIP_TO_PLAYSTATE
FlxG.switchState(new StoryMenuState());
#else
@ -60,6 +65,11 @@ class TitleState extends MusicBeatState
#end
}
var logoBl:FlxSprite;
var gfDance:FlxSprite;
var danceLeft:Bool = false;
var titleText:FlxSprite;
function startIntro()
{
if (!initialized)
@ -82,28 +92,58 @@ class TitleState extends MusicBeatState
FlxG.sound.playMusic('assets/music/freakyMenu' + TitleState.soundExt, 0);
FlxG.sound.music.fadeIn(4, 0, 0.7);
FlxG.save.bind('funkin', 'ninjamuffin99');
if (FlxG.save.data.weekUnlocked != null)
{
StoryMenuState.weekUnlocked = FlxG.save.data.weekUnlocked;
}
}
Conductor.changeBPM(102);
persistentUpdate = true;
var bg:FlxSprite = new FlxSprite().loadGraphic(AssetPaths.stageback__png);
bg.antialiasing = true;
bg.setGraphicSize(Std.int(bg.width * 0.6));
bg.updateHitbox();
var bg:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
// bg.antialiasing = true;
// bg.setGraphicSize(Std.int(bg.width * 0.6));
// bg.updateHitbox();
add(bg);
var logoBl:FlxSprite = new FlxSprite().loadGraphic(AssetPaths.logo__png);
logoBl.screenCenter();
logoBl.color = FlxColor.BLACK;
logoBl = new FlxSprite(-150, -100);
logoBl.frames = FlxAtlasFrames.fromSparrow(AssetPaths.logoBumpin__png, AssetPaths.logoBumpin__xml);
logoBl.antialiasing = true;
logoBl.animation.addByPrefix('bump', 'logo bumpin', 24);
logoBl.animation.play('bump');
logoBl.updateHitbox();
// logoBl.screenCenter();
// logoBl.color = FlxColor.BLACK;
gfDance = new FlxSprite(FlxG.width * 0.4, FlxG.height * 0.07);
gfDance.frames = FlxAtlasFrames.fromSparrow(AssetPaths.gfDanceTitle__png, AssetPaths.gfDanceTitle__xml);
gfDance.animation.addByIndices('danceLeft', 'gfDance', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false);
gfDance.animation.addByIndices('danceRight', 'gfDance', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false);
gfDance.antialiasing = true;
add(gfDance);
add(logoBl);
titleText = new FlxSprite(100, FlxG.height * 0.8);
titleText.frames = FlxAtlasFrames.fromSparrow(AssetPaths.titleEnter__png, AssetPaths.titleEnter__xml);
titleText.animation.addByPrefix('idle', "Press Enter to Begin", 24);
titleText.animation.addByPrefix('press', "ENTER PRESSED", 24);
titleText.antialiasing = true;
titleText.animation.play('idle');
titleText.updateHitbox();
// titleText.screenCenter(X);
add(titleText);
var logo:FlxSprite = new FlxSprite().loadGraphic(AssetPaths.logo__png);
logo.screenCenter();
logo.antialiasing = true;
add(logo);
// add(logo);
FlxTween.tween(logoBl, {y: logoBl.y + 50}, 0.6, {ease: FlxEase.quadInOut, type: PINGPONG});
FlxTween.tween(logo, {y: logoBl.y + 50}, 0.6, {ease: FlxEase.quadInOut, type: PINGPONG, startDelay: 0.1});
// FlxTween.tween(logoBl, {y: logoBl.y + 50}, 0.6, {ease: FlxEase.quadInOut, type: PINGPONG});
// FlxTween.tween(logo, {y: logoBl.y + 50}, 0.6, {ease: FlxEase.quadInOut, type: PINGPONG, startDelay: 0.1});
credGroup = new FlxGroup();
add(credGroup);
@ -129,6 +169,8 @@ class TitleState extends MusicBeatState
FlxTween.tween(credTextShit, {y: credTextShit.y + 20}, 2.9, {ease: FlxEase.quadInOut, type: PINGPONG});
FlxG.mouse.visible = false;
if (initialized)
skipIntro();
else
@ -155,6 +197,9 @@ class TitleState extends MusicBeatState
if (pressedEnter && !transitioning && skippedIntro)
{
NGio.unlockMedal(60960);
titleText.animation.play('press');
FlxG.camera.flash(FlxColor.WHITE, 1);
FlxG.sound.play('assets/sounds/confirmMenu' + TitleState.soundExt, 0.7);
@ -210,6 +255,14 @@ class TitleState extends MusicBeatState
{
super.beatHit();
logoBl.animation.play('bump');
danceLeft = !danceLeft;
if (danceLeft)
gfDance.animation.play('danceRight');
else
gfDance.animation.play('danceLeft');
FlxG.log.add(curBeat);
switch (curBeat)

View File

@ -0,0 +1,227 @@
package io.newgrounds;
import io.newgrounds.utils.Dispatcher;
import io.newgrounds.utils.AsyncHttp;
import io.newgrounds.objects.Error;
import io.newgrounds.objects.events.Result;
import io.newgrounds.objects.events.Result.ResultBase;
import io.newgrounds.objects.events.Response;
import haxe.ds.StringMap;
import haxe.Json;
/** A generic way to handle calls agnostic to their type */
interface ICallable {
public var component(default, null):String;
public function send():Void;
public function queue():Void;
public function destroy():Void;
}
class Call<T:ResultBase>
implements ICallable {
public var component(default, null):String;
var _core:NGLite;
var _properties:StringMap<Dynamic>;
var _parameters:StringMap<Dynamic>;
var _requireSession:Bool;
var _isSecure:Bool;
// --- BASICALLY SIGNALS
var _dataHandlers:TypedDispatcher<Response<T>>;
var _successHandlers:Dispatcher;
var _httpErrorHandlers:TypedDispatcher<Error>;
var _statusHandlers:TypedDispatcher<Int>;
public function new (core:NGLite, component:String, requireSession:Bool = false, isSecure:Bool = false) {
_core = core;
this.component = component;
_requireSession = requireSession;
_isSecure = isSecure && core.encryptionHandler != null;
}
/** adds a property to the input's object. **/
public function addProperty(name:String, value:Dynamic):Call<T> {
if (_properties == null)
_properties = new StringMap<Dynamic>();
_properties.set(name, value);
return this;
}
/** adds a parameter to the call's component object. **/
public function addComponentParameter(name:String, value:Dynamic, defaultValue:Dynamic = null):Call<T> {
if (value == defaultValue)//TODO?: allow sending null value
return this;
if (_parameters == null)
_parameters = new StringMap<Dynamic>();
_parameters.set(name, value);
return this;
}
/** Handy callback setter for chained call modifiers. Called when ng.io replies successfully */
public function addDataHandler(handler:Response<T>->Void):Call<T> {
if (_dataHandlers == null)
_dataHandlers = new TypedDispatcher<Response<T>>();
_dataHandlers.add(handler);
return this;
}
/** Handy callback setter for chained call modifiers. Called when ng.io replies successfully */
public function addSuccessHandler(handler:Void->Void):Call<T> {
if (_successHandlers == null)
_successHandlers = new Dispatcher();
_successHandlers.add(handler);
return this;
}
/** Handy callback setter for chained call modifiers. Called when ng.io does not reply for any reason */
public function addErrorHandler(handler:Error->Void):Call<T> {
if (_httpErrorHandlers == null)
_httpErrorHandlers = new TypedDispatcher<Error>();
_httpErrorHandlers.add(handler);
return this;
}
/** Handy callback setter for chained call modifiers. No idea when this is called; */
public function addStatusHandler(handler:Int->Void):Call<T> {//TODO:learn what this is for
if (_statusHandlers == null)
_statusHandlers = new TypedDispatcher<Int>();
_statusHandlers.add(handler);
return this;
}
/**
* Sends the call to the server, do not modify this object after calling this
* @param secure If encryption is enabled, it will encrypt the call.
**/
public function send():Void {
var data:Dynamic = {};
data.app_id = _core.appId;
data.call = {};
data.call.component = component;
if (_core.debug)
addProperty("debug", true);
if (_properties == null || !_properties.exists("session_id")) {
// --- HAS NO SESSION ID
if (_core.sessionId != null) {
// --- AUTO ADD SESSION ID
addProperty("session_id", _core.sessionId);
} else if (_requireSession){
_core.logError(new Error('cannot send "$component" call without a sessionId'));
return;
}
}
if (_properties != null) {
for (field in _properties.keys())
Reflect.setField(data, field, _properties.get(field));
}
if (_parameters != null) {
data.call.parameters = {};
for (field in _parameters.keys())
Reflect.setField(data.call.parameters, field, _parameters.get(field));
}
_core.logVerbose('Post - ${Json.stringify(data)}');
if (_isSecure) {
var secureData = _core.encryptionHandler(Json.stringify(data.call));
data.call = {};
data.call.secure = secureData;
_core.logVerbose(' secure - $secureData');
}
_core.markCallPending(this);
AsyncHttp.send(_core, Json.stringify(data), onData, onHttpError, onStatus);
}
/** Adds the call to the queue */
public function queue():Void {
_core.queueCall(this);
}
function onData(reply:String):Void {
_core.logVerbose('Reply - $reply');
if (_dataHandlers == null && _successHandlers == null)
return;
var response = new Response<T>(_core, reply);
if (_dataHandlers != null)
_dataHandlers.dispatch(response);
if (response.success && response.result.success && _successHandlers != null)
_successHandlers.dispatch();
destroy();
}
function onHttpError(message:String):Void {
_core.logError(message);
if (_httpErrorHandlers == null)
return;
var error = new Error(message);
_httpErrorHandlers.dispatch(error);
}
function onStatus(status:Int):Void {
if (_statusHandlers == null)
return;
_statusHandlers.dispatch(status);
}
public function destroy():Void {
_core = null;
_properties = null;
_parameters = null;
_dataHandlers = null;
_successHandlers = null;
_httpErrorHandlers = null;
_statusHandlers = null;
}
}

475
source/io/newgrounds/NG.hx Normal file
View File

@ -0,0 +1,475 @@
package io.newgrounds;
#if ng_lite
typedef NG = NGLite; //TODO: test and make lite UI
#else
import io.newgrounds.utils.Dispatcher;
import io.newgrounds.objects.Error;
import io.newgrounds.objects.events.Result.SessionResult;
import io.newgrounds.objects.events.Result.MedalListResult;
import io.newgrounds.objects.events.Result.ScoreBoardResult;
import io.newgrounds.objects.events.Response;
import io.newgrounds.objects.User;
import io.newgrounds.objects.Medal;
import io.newgrounds.objects.Session;
import io.newgrounds.objects.ScoreBoard;
import haxe.ds.IntMap;
import haxe.Timer;
/**
* The Newgrounds API for Haxe.
* Contains many things ripped from MSGhero
* - https://github.com/MSGhero/NG.hx
* @author GeoKureli
*/
class NG extends NGLite {
static public var core(default, null):NG;
static public var onCoreReady(default, null):Dispatcher = new Dispatcher();
// --- DATA
/** The logged in user */
public var user(get, never):User;
function get_user():User {
if (_session == null)
return null;
return _session.user;
}
public var passportUrl(get, never):String;
function get_passportUrl():String {
if (_session == null || _session.status != SessionStatus.REQUEST_LOGIN)
return null;
return _session.passportUrl;
}
public var medals(default, null):IntMap<Medal>;
public var scoreBoards(default, null):IntMap<ScoreBoard>;
// --- EVENTS
public var onLogin(default, null):Dispatcher;
public var onLogOut(default, null):Dispatcher;
public var onMedalsLoaded(default, null):Dispatcher;
public var onScoreBoardsLoaded(default, null):Dispatcher;
// --- MISC
public var loggedIn(default, null):Bool;
public var attemptingLogin(default, null):Bool;
var _loginCancelled:Bool;
var _passportCallback:Void->Void;
var _session:Session;
/**
* Iniitializes the API, call before utilizing any other component
* @param appId The unique ID of your app as found in the 'API Tools' tab of your Newgrounds.com project.
* @param sessionId A unique session id used to identify the active user.
**/
public function new(appId = "test", sessionId:String = null, ?onSessionFail:Error->Void) {
_session = new Session(this);
onLogin = new Dispatcher();
onLogOut = new Dispatcher();
onMedalsLoaded = new Dispatcher();
onScoreBoardsLoaded = new Dispatcher();
attemptingLogin = sessionId != null;
super(appId, sessionId, onSessionFail);
}
/**
* Creates NG.core, the heart and soul of the API. This is not the only way to create an instance,
* nor is NG a forced singleton, but it's the only way to set the static NG.core.
**/
static public function create(appId = "test", sessionId:String = null, ?onSessionFail:Error->Void):Void {
core = new NG(appId, sessionId, onSessionFail);
onCoreReady.dispatch();
}
/**
* Creates NG.core, and tries to create a session. This is not the only way to create an instance,
* nor is NG a forced singleton, but it's the only way to set the static NG.core.
**/
static public function createAndCheckSession
( appId = "test"
, backupSession:String = null
, ?onSessionFail:Error->Void
):Void {
var session = NGLite.getSessionId();
if (session == null)
session = backupSession;
create(appId, session, onSessionFail);
core.host = getHost();
if (core.sessionId != null)
core.attemptingLogin = true;
}
// -------------------------------------------------------------------------------------------
// APP
// -------------------------------------------------------------------------------------------
override function checkInitialSession(failHandler:Error->Void, response:Response<SessionResult>):Void {
onSessionReceive(response, null, null, failHandler);
}
/**
* Begins the login process
*
* @param onSuccess Called when the login is a success
* @param onPending Called when the passportUrl has been identified, call NG.core.openPassportLink
* to open the link continue the process. Leave as null to open the url automatically
* NOTE: Browser games must open links on click events or else it will be blocked by
* the popup blocker.
* @param onFail
* @param onCancel Called when the user denies the passport connection.
*/
public function requestLogin
( onSuccess:Void->Void = null
, onPending:Void->Void = null
, onFail :Error->Void = null
, onCancel :Void->Void = null
):Void {
if (attemptingLogin) {
logError("cannot request another login until the previous attempt is complete");
return;
}
if (loggedIn) {
logError("cannot log in, already logged in");
return;
}
attemptingLogin = true;
_loginCancelled = false;
_passportCallback = null;
var call = calls.app.startSession(true)
.addDataHandler(onSessionReceive.bind(_, onSuccess, onPending, onFail, onCancel));
if (onFail != null)
call.addErrorHandler(onFail);
call.send();
}
function onSessionReceive
( response :Response<SessionResult>
, onSuccess:Void->Void = null
, onPending:Void->Void = null
, onFail :Error->Void = null
, onCancel :Void->Void = null
):Void {
if (!response.success || !response.result.success) {
sessionId = null;
endLoginAndCall(null);
if (onFail != null)
onFail(!response.success ? response.error : response.result.error);
return;
}
_session.parse(response.result.data.session);
sessionId = _session.id;
logVerbose('session started - status: ${_session.status}');
if (_session.status == SessionStatus.REQUEST_LOGIN) {
_passportCallback = checkSession.bind(null, onSuccess, onCancel);
if (onPending != null)
onPending();
else
openPassportUrl();
} else
checkSession(null, onSuccess, onCancel);
}
/**
* Call this once the passport link is established and it will load the passport URL and
* start checking for session connect periodically
*/
public function openPassportUrl():Void {
if (passportUrl != null) {
logVerbose('loading passport: ${passportUrl}');
openPassportHelper(passportUrl);
onPassportUrlOpen();
} else
logError("Cannot open passport");
}
static function openPassportHelper(url:String):Void {
var window = "_blank";
#if flash
flash.Lib.getURL(new flash.net.URLRequest(url), window);
#elseif (js && html5)
js.Browser.window.open(url, window);
#elseif desktop
#if (sys && windows)
Sys.command("start", ["", url]);
#elseif mac
Sys.command("/usr/bin/open", [url]);
#elseif linux
Sys.command("/usr/bin/xdg-open", [path, "&"]);
#end
#elseif android
JNI.createStaticMethod
( "org/haxe/lime/GameActivity"
, "openURL"
, "(Ljava/lang/String;Ljava/lang/String;)V"
) (url, window);
#end
}
/**
* Call this once the passport link is established and it will start checking for session connect periodically
*/
public function onPassportUrlOpen():Void {
if (_passportCallback != null)
_passportCallback();
_passportCallback = null;
}
function checkSession(response:Response<SessionResult>, onSucceess:Void->Void, onCancel:Void->Void):Void {
if (response != null) {
if (!response.success || !response.result.success) {
log("login cancelled via passport");
endLoginAndCall(onCancel);
return;
}
_session.parse(response.result.data.session);
}
if (_session.status == SessionStatus.USER_LOADED) {
loggedIn = true;
endLoginAndCall(onSucceess);
onLogin.dispatch();
} else if (_session.status == SessionStatus.REQUEST_LOGIN){
var call = calls.app.checkSession()
.addDataHandler(checkSession.bind(_, onSucceess, onCancel));
// Wait 3 seconds and try again
timer(3.0,
function():Void {
// Check if cancelLoginRequest was called
if (!_loginCancelled)
call.send();
else {
log("login cancelled via cancelLoginRequest");
endLoginAndCall(onCancel);
}
}
);
} else
// The user cancelled the passport
endLoginAndCall(onCancel);
}
public function cancelLoginRequest():Void {
if (attemptingLogin)
_loginCancelled = true;
}
function endLoginAndCall(callback:Void->Void):Void {
attemptingLogin = false;
_loginCancelled = false;
if (callback != null)
callback();
}
public function logOut(onComplete:Void->Void = null):Void {
var call = calls.app.endSession()
.addSuccessHandler(onLogOutSuccessful);
if (onComplete != null)
call.addSuccessHandler(onComplete);
call.addSuccessHandler(onLogOut.dispatch)
.send();
}
function onLogOutSuccessful():Void {
_session.expire();
sessionId = null;
loggedIn = false;
}
// -------------------------------------------------------------------------------------------
// MEDALS
// -------------------------------------------------------------------------------------------
public function requestMedals(onSuccess:Void->Void = null, onFail:Error->Void = null):Void {
var call = calls.medal.getList()
.addDataHandler(onMedalsReceived);
if (onSuccess != null)
call.addSuccessHandler(onSuccess);
if (onFail != null)
call.addErrorHandler(onFail);
call.send();
}
function onMedalsReceived(response:Response<MedalListResult>):Void {
if (!response.success || !response.result.success)
return;
var idList:Array<Int> = new Array<Int>();
if (medals == null) {
medals = new IntMap<Medal>();
for (medalData in response.result.data.medals) {
var medal = new Medal(this, medalData);
medals.set(medal.id, medal);
idList.push(medal.id);
}
} else {
for (medalData in response.result.data.medals) {
medals.get(medalData.id).parse(medalData);
idList.push(medalData.id);
}
}
logVerbose('${response.result.data.medals.length} Medals received [${idList.join(", ")}]');
onMedalsLoaded.dispatch();
}
// -------------------------------------------------------------------------------------------
// SCOREBOARDS
// -------------------------------------------------------------------------------------------
public function requestScoreBoards(onSuccess:Void->Void = null, onFail:Error->Void = null):Void {
if (scoreBoards != null) {
log("aborting scoreboard request, all scoreboards are loaded");
if (onSuccess != null)
onSuccess();
return;
}
var call = calls.scoreBoard.getBoards()
.addDataHandler(onBoardsReceived);
if (onSuccess != null)
call.addSuccessHandler(onSuccess);
if (onFail != null)
call.addErrorHandler(onFail);
call.send();
}
function onBoardsReceived(response:Response<ScoreBoardResult>):Void {
if (!response.success || !response.result.success)
return;
var idList:Array<Int> = new Array<Int>();
if (scoreBoards == null) {
scoreBoards = new IntMap<ScoreBoard>();
for (boardData in response.result.data.scoreboards) {
var board = new ScoreBoard(this, boardData);
scoreBoards.set(board.id, board);
idList.push(board.id);
}
}
logVerbose('${response.result.data.scoreboards.length} ScoreBoards received [${idList.join(", ")}]');
onScoreBoardsLoaded.dispatch();
}
// -------------------------------------------------------------------------------------------
// HELPERS
// -------------------------------------------------------------------------------------------
function timer(delay:Float, callback:Void->Void):Void {
var timer = new Timer(Std.int(delay * 1000));
timer.run = function func():Void {
timer.stop();
callback();
}
}
static var urlParser:EReg = ~/^(?:http[s]?:\/\/)?([^:\/\s]+)(:[0-9]+)?((?:\/\w+)*\/)([\w\-\.]+[^#?\s]+)([^#\s]*)?(#[\w\-]+)?$/i;//TODO:trim
/** Used to get the current web host of your game. */
static public function getHost():String {
var url = NGLite.getUrl();
if (url == null || url == "")
return "<AppView>";
if (url.indexOf("file") == 0)
return "<LocalHost>";
if (urlParser.match(url))
return urlParser.matched(1);
return "<Unknown>";
}
}
#end

View File

@ -0,0 +1,287 @@
package io.newgrounds;
import haxe.crypto.Base64;
import haxe.io.Bytes;
import haxe.PosInfos;
import io.newgrounds.Call.ICallable;
import io.newgrounds.components.ComponentList;
import io.newgrounds.crypto.EncryptionFormat;
import io.newgrounds.crypto.Cipher;
import io.newgrounds.crypto.Rc4;
import io.newgrounds.objects.Error;
import io.newgrounds.objects.events.Response;
import io.newgrounds.objects.events.Result.ResultBase;
import io.newgrounds.objects.events.Result.SessionResult;
import io.newgrounds.utils.Dispatcher;
#if !(html5 || flash || desktop || neko)
#error "Target not supported, use: Flash, JS/HTML5, cpp or maybe neko";
#end
/**
* The barebones NG.io API. Allows API calls with code completion
* and retrieves server data via strongly typed Objects
*
* Contains many things ripped from MSGhero's repo
* - https://github.com/MSGhero/NG.hx
*
* @author GeoKureli
*/
class NGLite {
static public var core(default, null):NGLite;
static public var onCoreReady(default, null):Dispatcher = new Dispatcher();
/** Enables verbose logging */
public var verbose:Bool;
public var debug:Bool;
/** The unique ID of your app as found in the 'API Tools' tab of your Newgrounds.com project. */
public var appId(default, null):String;
/** The name of the host the game is being played on */
public var host:String;
@:isVar
public var sessionId(default, set):String;
function set_sessionId(value:String):String {
return this.sessionId = value == "" ? null : value;
}
/** Components used to call the NG server directly */
public var calls(default, null):ComponentList;
/**
* Converts an object to an encrypted string that can be decrypted by the server.
* Set your preffered encrypter here,
* or just call setDefaultEcryptionHandler with your app's encryption settings
**/
public var encryptionHandler:String->String;
/**
* Iniitializes the API, call before utilizing any other component
* @param appId The unique ID of your app as found in the 'API Tools' tab of your Newgrounds.com project.
* @param sessionId A unique session id used to identify the active user.
**/
public function new(appId = "test", sessionId:String = null, ?onSessionFail:Error->Void) {
this.appId = appId;
this.sessionId = sessionId;
calls = new ComponentList(this);
if (this.sessionId != null) {
calls.app.checkSession()
.addDataHandler(checkInitialSession.bind(onSessionFail))
.addErrorHandler(initialSessionFail.bind(onSessionFail))
.send();
}
}
function checkInitialSession(onFail:Error->Void, response:Response<SessionResult>):Void {
if (!response.success || !response.result.success || response.result.data.session.expired) {
initialSessionFail(onFail, response.success ? response.result.error : response.error);
}
}
function initialSessionFail(onFail:Error->Void, error:Error):Void {
sessionId = null;
if (onFail != null)
onFail(error);
}
/**
* Creates NG.core, the heart and soul of the API. This is not the only way to create an instance,
* nor is NG a forced singleton, but it's the only way to set the static NG.core.
**/
static public function create(appId = "test", sessionId:String = null, ?onSessionFail:Error->Void):Void {
core = new NGLite(appId, sessionId, onSessionFail);
onCoreReady.dispatch();
}
/**
* Creates NG.core, and tries to create a session. This is not the only way to create an instance,
* nor is NG a forced singleton, but it's the only way to set the static NG.core.
**/
static public function createAndCheckSession
( appId = "test"
, backupSession:String = null
, ?onSessionFail:Error->Void
):Void {
var session = getSessionId();
if (session == null)
session = backupSession;
create(appId, session, onSessionFail);
}
inline static public function getUrl():String {
#if html5
return js.Browser.document.location.href;
#elseif flash
return flash.Lib.current.stage.loaderInfo != null
? flash.Lib.current.stage.loaderInfo.url
: null;
#else
return null;
#end
}
static public function getSessionId():String {
#if html5
var url = getUrl();
// Check for URL params
var index = url.indexOf("?");
if (index != -1) {
// Check for session ID in params
for (param in url.substr(index + 1).split("&")) {
index = param.indexOf("=");
if (index != -1 && param.substr(0, index) == "ngio_session_id")
return param.substr(index + 1);
}
}
#elseif flash
if (flash.Lib.current.stage.loaderInfo != null
&& Reflect.hasField(flash.Lib.current.stage.loaderInfo.parameters, "ngio_session_id"))
return Reflect.field(flash.Lib.current.stage.loaderInfo.parameters, "ngio_session_id");
#end
return null;
// --- EXAMPLE LOADER PARAMS
//{ "1517703669" : ""
//, "ng_username" : "GeoKureli"
//, "NewgroundsAPI_SessionID" : "F1LusbG6P8Qf91w7zeUE37c1752563f366688ac6153996d12eeb111a2f60w2xn"
//, "NewgroundsAPI_PublisherID" : 1
//, "NewgroundsAPI_UserID" : 488329
//, "NewgroundsAPI_SandboxID" : "5a76520e4ae1e"
//, "ngio_session_id" : "0c6c4e02567a5116734ba1a0cd841dac28a42e79302290"
//, "NewgroundsAPI_UserName" : "GeoKureli"
//}
}
// -------------------------------------------------------------------------------------------
// CALLS
// -------------------------------------------------------------------------------------------
var _queuedCalls:Array<ICallable> = new Array<ICallable>();
var _pendingCalls:Array<ICallable> = new Array<ICallable>();
@:allow(io.newgrounds.Call)
@:generic
function queueCall<T:ResultBase>(call:Call<T>):Void {
logVerbose('queued - ${call.component}');
_queuedCalls.push(call);
checkQueue();
}
@:allow(io.newgrounds.Call)
@:generic
function markCallPending<T:ResultBase>(call:Call<T>):Void {
_pendingCalls.push(call);
call.addDataHandler(function (_):Void { onCallComplete(call); });
call.addErrorHandler(function (_):Void { onCallComplete(call); });
}
function onCallComplete(call:ICallable):Void {
_pendingCalls.remove(call);
checkQueue();
}
function checkQueue():Void {
if (_pendingCalls.length == 0 && _queuedCalls.length > 0)
_queuedCalls.shift().send();
}
// -------------------------------------------------------------------------------------------
// LOGGING / ERRORS
// -------------------------------------------------------------------------------------------
/** Called internally, set this to your preferred logging method */
dynamic public function log(any:Dynamic, ?pos:PosInfos):Void {//TODO: limit access via @:allow
haxe.Log.trace('[Newgrounds API] :: ${any}', pos);
}
/** used internally, logs if verbose is true */
inline public function logVerbose(any:Dynamic, ?pos:PosInfos):Void {//TODO: limit access via @:allow
if (verbose)
log(any, pos);
}
/** Used internally. Logs by default, set this to your preferred error handling method */
dynamic public function logError(any:Dynamic, ?pos:PosInfos):Void {//TODO: limit access via @:allow
log('Error: $any', pos);
}
/** used internally, calls log error if the condition is false. EX: if (assert(data != null, "null data")) */
inline public function assert(condition:Bool, msg:Dynamic, ?pos:PosInfos):Bool {//TODO: limit access via @:allow
if (!condition)
logError(msg, pos);
return condition;
}
// -------------------------------------------------------------------------------------------
// ENCRYPTION
// -------------------------------------------------------------------------------------------
/** Sets */
public function initEncryption
( key :String
, cipher:Cipher = Cipher.RC4
, format:EncryptionFormat = EncryptionFormat.BASE_64
):Void {
if (cipher == Cipher.NONE)
encryptionHandler = null;
else if (cipher == Cipher.RC4)
encryptionHandler = encryptRc4.bind(key, format);
else
throw "aes not yet implemented";
}
function encryptRc4(key:String, format:EncryptionFormat, data:String):String {
if (format == EncryptionFormat.HEX)
throw "hex format not yet implemented";
var keyBytes:Bytes;
if (format == EncryptionFormat.BASE_64)
keyBytes = Base64.decode(key);
else
keyBytes = null;//TODO
var dataBytes = new Rc4(keyBytes).crypt(Bytes.ofString(data));
if (format == EncryptionFormat.BASE_64)
return Base64.encode(dataBytes);
return null;
}
}

View File

@ -0,0 +1,44 @@
package io.newgrounds.components;
import io.newgrounds.objects.events.Result;
import io.newgrounds.objects.events.Result.SessionResult;
import io.newgrounds.NGLite;
class AppComponent extends Component {
public function new (core:NGLite) { super(core); }
public function startSession(force:Bool = false):Call<SessionResult> {
return new Call<SessionResult>(_core, "App.startSession")
.addComponentParameter("force", force, false);
}
public function checkSession():Call<SessionResult> {
return new Call<SessionResult>(_core, "App.checkSession", true);
}
public function endSession():Call<SessionResult> {
return new Call<SessionResult>(_core, "App.endSession", true);
}
public function getCurrentVersion(version:String):Call<GetCurrentVersionResult> {
return new Call<GetCurrentVersionResult>(_core, "App.getCurrentVersion")
.addComponentParameter("version", version);
}
public function getHostLicense():Call<GetHostResult> {
return new Call<GetHostResult>(_core, "App.getHostLicense")
.addComponentParameter("host", _core.host);
}
public function logView():Call<ResultBase> {
return new Call<ResultBase>(_core, "App.logView")
.addComponentParameter("host", _core.host);
}
}

View File

@ -0,0 +1,13 @@
package io.newgrounds.components;
import io.newgrounds.NGLite;
class Component {
var _core:NGLite;
public function new(core:NGLite) {
this._core = core;
}
}

View File

@ -0,0 +1,25 @@
package io.newgrounds.components;
class ComponentList {
var _core:NGLite;
// --- COMPONENTS
public var medal : MedalComponent;
public var app : AppComponent;
public var event : EventComponent;
public var scoreBoard: ScoreBoardComponent;
public var loader : LoaderComponent;
public var gateway : GatewayComponent;
public function new(core:NGLite) {
_core = core;
medal = new MedalComponent (_core);
app = new AppComponent (_core);
event = new EventComponent (_core);
scoreBoard = new ScoreBoardComponent(_core);
loader = new LoaderComponent (_core);
gateway = new GatewayComponent (_core);
}
}

View File

@ -0,0 +1,16 @@
package io.newgrounds.components;
import io.newgrounds.objects.events.Result.LogEventResult;
import io.newgrounds.NGLite;
class EventComponent extends Component {
public function new (core:NGLite){ super(core); }
public function logEvent(eventName:String):Call<LogEventResult> {
return new Call<LogEventResult>(_core, "Event.logEvent")
.addComponentParameter("event_name", eventName)
.addComponentParameter("host", _core.host);
}
}

View File

@ -0,0 +1,25 @@
package io.newgrounds.components;
import io.newgrounds.objects.events.Result;
import io.newgrounds.NGLite;
class GatewayComponent extends Component {
public function new (core:NGLite){ super(core); }
public function getDatetime():Call<GetDateTimeResult> {
return new Call<GetDateTimeResult>(_core, "Gateway.getDatetime");
}
public function getVersion():Call<GetVersionResult> {
return new Call<GetVersionResult>(_core, "Gateway.getVersion");
}
public function ping():Call<PingResult> {
return new Call<PingResult>(_core, "Gateway.ping");
}
}

View File

@ -0,0 +1,44 @@
package io.newgrounds.components;
import io.newgrounds.objects.events.Result;
import io.newgrounds.NGLite;
class LoaderComponent extends Component {
public function new (core:NGLite){ super(core); }
public function loadAuthorUrl(redirect:Bool = false):Call<ResultBase> {
return new Call<ResultBase>(_core, "Loader.loadAuthorUrl")
.addComponentParameter("host", _core.host)
.addComponentParameter("redirect", redirect, true);
}
public function loadMoreGames(redirect:Bool = false):Call<ResultBase> {
return new Call<ResultBase>(_core, "Loader.loadMoreGames")
.addComponentParameter("host", _core.host)
.addComponentParameter("redirect", redirect, true);
}
public function loadNewgrounds(redirect:Bool = false):Call<ResultBase> {
return new Call<ResultBase>(_core, "Loader.loadNewgrounds")
.addComponentParameter("host", _core.host)
.addComponentParameter("redirect", redirect, true);
}
public function loadOfficialUrl(redirect:Bool = false):Call<ResultBase> {
return new Call<ResultBase>(_core, "Loader.loadOfficialUrl")
.addComponentParameter("host", _core.host)
.addComponentParameter("redirect", redirect, true);
}
public function loadReferral(redirect:Bool = false):Call<ResultBase> {
return new Call<ResultBase>(_core, "Loader.loadReferral")
.addComponentParameter("host", _core.host)
.addComponentParameter("redirect", redirect, true);
}
}

View File

@ -0,0 +1,21 @@
package io.newgrounds.components;
import io.newgrounds.objects.events.Result;
import io.newgrounds.Call;
import io.newgrounds.NGLite;
class MedalComponent extends Component {
public function new(core:NGLite):Void { super(core); }
public function unlock(id:Int):Call<MedalUnlockResult> {
return new Call<MedalUnlockResult>(_core, "Medal.unlock", true, true)
.addComponentParameter("id", id);
}
public function getList():Call<MedalListResult> {
return new Call<MedalListResult>(_core, "Medal.getList");
}
}

View File

@ -0,0 +1,114 @@
package io.newgrounds.components;
import io.newgrounds.objects.User;
import io.newgrounds.objects.events.Response;
import io.newgrounds.objects.events.Result;
import io.newgrounds.objects.events.Result.ScoreBoardResult;
import io.newgrounds.objects.events.Result.ScoreResult;
import io.newgrounds.NGLite;
import io.newgrounds.objects.ScoreBoard;
import haxe.ds.IntMap;
class ScoreBoardComponent extends Component {
public var allById:IntMap<ScoreBoard>;
public function new (core:NGLite){ super(core); }
// -------------------------------------------------------------------------------------------
// GET SCORES
// -------------------------------------------------------------------------------------------
public function getBoards():Call<ScoreBoardResult> {
return new Call<ScoreBoardResult>(_core, "ScoreBoard.getBoards");
}
/*function onBoardsReceive(response:Response<ScoreBoardResult>):Void {
if (!response.result.success)
return;
allById = new IntMap<ScoreBoard>();
for (boardData in response.result.scoreboards)
createBoard(boardData);
_core.log('${response.result.scoreboards.length} ScoreBoards loaded');
}*/
// -------------------------------------------------------------------------------------------
// GET SCORES
// -------------------------------------------------------------------------------------------
public function getScores
( id :Int
, limit :Int = 10
, skip :Int = 0
, period:Period = Period.DAY
, social:Bool = false
, tag :String = null
, user :Dynamic = null
):Call<ScoreResult> {
if (user != null && !Std.is(user, String) && !Std.is(user, Int))
user = user.id;
return new Call<ScoreResult>(_core, "ScoreBoard.getScores")
.addComponentParameter("id" , id )
.addComponentParameter("limit" , limit , 10)
.addComponentParameter("skip" , skip , 0)
.addComponentParameter("period", period, Period.DAY)
.addComponentParameter("social", social, false)
.addComponentParameter("tag" , tag , null)
.addComponentParameter("user" , user , null);
}
// -------------------------------------------------------------------------------------------
// POST SCORE
// -------------------------------------------------------------------------------------------
public function postScore(id:Int, value:Int, tag:String = null):Call<PostScoreResult> {
return new Call<PostScoreResult>(_core, "ScoreBoard.postScore", true, true)
.addComponentParameter("id" , id)
.addComponentParameter("value", value)
.addComponentParameter("tag" , tag , null);
}
/*function onScorePosted(response:Response<ResultBase>):Void {
if (!response.result.success)
return;
allById = new IntMap<ScoreBoard>();
//createBoard(data.data.scoreBoard).parseScores(data.data.scores);
}*/
inline function createBoard(data:Dynamic):ScoreBoard {
var board = new ScoreBoard(_core, data);
_core.logVerbose('created $board');
allById.set(board.id, board);
return board;
}
}
@:enum
abstract Period(String) to String from String{
/** Indicates scores are from the current day. */
var DAY = "D";
/** Indicates scores are from the current week. */
var WEEK = "W";
/** Indicates scores are from the current month. */
var MONTH = "M";
/** Indicates scores are from the current year. */
var YEAR = "Y";
/** Indicates scores are from all-time. */
var ALL = "A";
}

View File

@ -0,0 +1,8 @@
package io.newgrounds.crypto;
@:enum
abstract Cipher(String) to String{
var NONE = "none";
var AES_128 = "aes128";
var RC4 = "rc4";
}

View File

@ -0,0 +1,7 @@
package io.newgrounds.crypto;
@:enum
abstract EncryptionFormat(String) to String {
var BASE_64 = "base64";
var HEX = "hex";
}

View File

@ -0,0 +1,68 @@
package io.newgrounds.crypto;
import haxe.io.Bytes;
/**
* The following was straight-up ganked from https://github.com/iskolbin/rc4hx
*
* You da real MVP iskolbin...
*
* The MIT License (MIT)
*
* Copyright (c) 2015 iskolbin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
**/
class Rc4 {
var perm = Bytes.alloc( 256 );
var index1: Int = 0;
var index2: Int = 0;
public function new( key: Bytes ) {
for ( i in 0...256 ) {
perm.set( i, i );
}
var j: Int = 0;
for ( i in 0...256 ) {
j = ( j + perm.get( i ) + key.get( i % key.length )) % 256;
swap( i, j );
}
}
inline function swap( i: Int, j: Int ): Void {
var temp = perm.get( i );
perm.set( i, perm.get( j ));
perm.set( j, temp );
}
public function crypt( input: Bytes ): Bytes {
var output = Bytes.alloc( input.length );
for ( i in 0...input.length ) {
index1 = ( index1 + 1 ) % 256;
index2 = ( index2 + perm.get( index1 )) % 256;
swap( index1, index2 );
var j = ( perm.get( index1 ) + perm.get( index2 )) % 256;
output.set( i, input.get( i ) ^ perm.get( j ));
}
return output;
}
}

View File

@ -0,0 +1,20 @@
package io.newgrounds.objects;
class Error {
public var code(default, null):Int;
public var message(default, null):String;
public function new (message:String, code:Int = 0) {
this.message = message;
this.code = code;
}
public function toString():String {
if (code > 0)
return '#$code - $message';
return message;
}
}

View File

@ -0,0 +1,118 @@
package io.newgrounds.objects;
import io.newgrounds.objects.events.Response;
import io.newgrounds.objects.events.Result.MedalUnlockResult;
import io.newgrounds.utils.Dispatcher;
import io.newgrounds.NGLite;
class Medal extends Object {
inline static public var EASY :Int = 1;
inline static public var MODERATE :Int = 2;
inline static public var CHALLENGING:Int = 3;
inline static public var DIFFICULT :Int = 4;
inline static public var BRUTAL :Int = 5;
static var difficultyNames:Array<String> =
[ "Easy"
, "Moderate"
, "Challenging"
, "Difficult"
, "Brutal"
];
// --- FROM SERVER
public var id (default, null):Int;
public var name (default, null):String;
public var description(default, null):String;
public var icon (default, null):String;
public var value (default, null):Int;
public var difficulty (default, null):Int;
public var secret (default, null):Bool;
public var unlocked (default, null):Bool;
// --- HELPERS
public var difficultyName(get, never):String;
public var onUnlock:Dispatcher;
public function new(core:NGLite, data:Dynamic = null):Void {
onUnlock = new Dispatcher();
super(core, data);
}
@:allow(io.newgrounds.NG)
override function parse(data:Dynamic):Void {
var wasLocked = !unlocked;
id = data.id;
name = data.name;
description = data.description;
icon = data.icon;
value = data.value;
difficulty = data.difficulty;
secret = data.secret == 1;
unlocked = data.unlocked;
super.parse(data);
if (wasLocked && unlocked)
onUnlock.dispatch();
}
public function sendUnlock():Void {
if (_core.sessionId == null) {
// --- Unlock regardless, show medal popup to encourage NG signup
unlocked = true;
onUnlock.dispatch();
//TODO: save unlock in local save
}
_core.calls.medal.unlock(id)
.addDataHandler(onUnlockResponse)
.send();
}
function onUnlockResponse(response:Response<MedalUnlockResult>):Void {
if (response.success && response.result.success) {
parse(response.result.data.medal);
// --- Unlock response doesn't include unlock=true, so parse won't change it.
if (!unlocked) {
unlocked = true;
onUnlock.dispatch();
}
}
}
/** Locks the medal on the client and sends an unlock request, Server responds the same either way. */
public function sendDebugUnlock():Void {
if (NG.core.sessionId == null) {
onUnlock.dispatch();
} else {
unlocked = false;
sendUnlock();
}
}
public function get_difficultyName():String {
return difficultyNames[difficulty - 1];
}
public function toString():String {
return 'Medal: $id@$name (${unlocked ? "unlocked" : "locked"}, $value pts, $difficultyName).';
}
}

View File

@ -0,0 +1,33 @@
package io.newgrounds.objects;
import io.newgrounds.utils.Dispatcher;
import io.newgrounds.NGLite;
class Object {
var _core:NGLite;
public var onUpdate(default, null):Dispatcher;
public function new(core:NGLite, data:Dynamic = null) {
this._core = core;
onUpdate = new Dispatcher();
if (data != null)
parse(data);
}
@:allow(io.newgrounds.NGLite)
function parse(data:Dynamic):Void {
onUpdate.dispatch();
}
public function destroy():Void {
_core = null;
}
}

View File

@ -0,0 +1,17 @@
package io.newgrounds.objects;
/** We don't want to serialize scores since there's a bajillion of them. */
typedef Score = {
/** The value value in the format selected in your scoreboard settings. */
var formatted_value:String;
/** The tag attached to this value (if any). */
var tag:String;
/** The user who earned value. If this property is absent, the value belongs to the active user. */
var user:User;
/** The integer value of the value. */
var value:Int;
}

View File

@ -0,0 +1,76 @@
package io.newgrounds.objects;
import io.newgrounds.components.ScoreBoardComponent.Period;
import io.newgrounds.objects.events.Response;
import io.newgrounds.objects.events.Result;
import io.newgrounds.objects.events.Result.ScoreResult;
import io.newgrounds.NGLite;
class ScoreBoard extends Object {
public var scores(default, null):Array<Score>;
/** The numeric ID of the scoreboard.*/
public var id(default, null):Int;
/** The name of the scoreboard. */
public var name(default, null):String;
public function new(core:NGLite, data:Dynamic):Void {super(core, data); }
override function parse(data:Dynamic):Void {
id = data.id;
name = data.name;
super.parse(data);
}
/**
* Fetches score data from the server, this removes all of the existing scores cached
*
* We don't unify the old and new scores because a user's rank or score may change between requests
*/
public function requestScores
( limit :Int = 10
, skip :Int = 0
, period:Period = Period.ALL
, social:Bool = false
, tag :String = null
, user :Dynamic = null
):Void {
_core.calls.scoreBoard.getScores(id, limit, skip, period, social, tag, user)
.addDataHandler(onScoresReceived)
.send();
}
function onScoresReceived(response:Response<ScoreResult>):Void {
if (!response.success || !response.result.success)
return;
scores = response.result.data.scores;
_core.logVerbose('received ${scores.length} scores');
onUpdate.dispatch();
}
public function postScore(value :Int, tag:String = null):Void {
_core.calls.scoreBoard.postScore(id, value, tag)
.addDataHandler(onScorePosted)
.send();
}
function onScorePosted(response:Response<PostScoreResult>):Void {
}
public function toString():String {
return 'ScoreBoard: $id@$name';
}
}

View File

@ -0,0 +1,65 @@
package io.newgrounds.objects;
class Session extends Object {
/** If true, the session_id is expired. Use App.startSession to get a new one.*/
public var expired(default, null):Bool;
/** A unique session identifier */
public var id(default, null):String;
/** If the session has no associated user but is not expired, this property will provide a URL that can be used to sign the user in. */
public var passportUrl(default, null):String;
/** If true, the user would like you to remember their session id. */
public var remember(default, null):Bool;
/** If the user has not signed in, or granted access to your app, this will be null */
public var user(default, null):User;
//TODO:desciption
public var status(get, never):SessionStatus;
public function new(core:NGLite, data:Dynamic = null) { super(core, data); }
override public function parse(data:Dynamic):Void {
id = data.id;
expired = data.expired;
passportUrl = data.passport_url;
remember = data.remember;
// --- KEEP THE SAME INSTANCE
if (user == null)
user = data.user;
// TODO?: update original user instance with new data. (probly not)
super.parse(data);
}
public function get_status():SessionStatus {
if (expired || id == null || id == "")
return SessionStatus.SESSION_EXPIRED;
if (user != null && user.name != null && user.name != "")
return SessionStatus.USER_LOADED;
return SessionStatus.REQUEST_LOGIN;
}
public function expire():Void {
expired = true;
id = null;
user = null;
}
}
@:enum
abstract SessionStatus(String) {
var SESSION_EXPIRED = "session-expired";
var REQUEST_LOGIN = "request-login";
var USER_LOADED = "user-loaded";
}

View File

@ -0,0 +1,19 @@
package io.newgrounds.objects;
typedef User = {
/** The user's icon images. */
var icons:UserIcons;
/** The user's numeric ID. */
var id:Int;
/** The user's textual name. */
var name:String;
/** Returns true if the user has a Newgrounds Supporter upgrade. */
var supporter:Bool;
/** The user's NG profile url. */
var url:String;
}

View File

@ -0,0 +1,14 @@
package io.newgrounds.objects;
typedef UserIcons = {
/**The URL of the user's large icon. */
var large:String;
/** The URL of the user's medium icon. */
var medium:String;
/** The URL of the user's small icon. */
var small:String;
}

View File

@ -0,0 +1,43 @@
package io.newgrounds.objects.events;
import io.newgrounds.objects.events.Result.ResultBase;
import haxe.Json;
import io.newgrounds.objects.Error;
typedef DebugResponse = {
var exec_time:Int;
var input:Dynamic;
}
class Response<T:ResultBase> {
public var success(default, null):Bool;
public var error(default, null):Error;
public var debug(default, null):DebugResponse;
public var result(default, null):Result<T>;
public function new (core:NGLite, reply:String) {
var data:Dynamic;
try {
data = Json.parse(reply);
} catch (e:Dynamic) {
data = Json.parse('{"success":false,"error":{"message":"${Std.string(reply)}","code":0}}');
}
success = data.success;
debug = data.debug;
if (!success) {
error = new Error(data.error.message, data.error.code);
core.logError('Call unseccessful: $error');
return;
}
result = new Result<T>(core, data.result);
}
}

View File

@ -0,0 +1,109 @@
package io.newgrounds.objects.events;
class Result<T:ResultBase> {
public var echo(default, null):String;
public var component(default, null):String;
public var data(default, null):T;
public var success(default, null):Bool;
public var debug(default, null):Bool;
public var error(default, null):Error;
public function new(core:NGLite, data:Dynamic) {
echo = data.echo;
component = data.component;
data = data.data;
success = data.success;
debug = data.debug;
if(!data.success) {
error = new Error(data.error.message, data.error.code);
core.logError('$component fail: $error');
} else
this.data = data;
}
}
typedef ResultBase = { };
typedef SessionResult = {
> ResultBase,
var session:Dynamic;
}
typedef GetHostResult = {
> ResultBase,
var host_approved:Bool;
}
typedef GetCurrentVersionResult = {
> ResultBase,
var current_version:String;
var client_deprecated:Bool;
}
typedef LogEventResult = {
> ResultBase,
var event_name:String;
}
typedef GetDateTimeResult = {
> ResultBase,
var datetime:String;
}
typedef GetVersionResult = {
> ResultBase,
var version:String;
}
typedef PingResult = {
> ResultBase,
var pong:String;
}
typedef MedalListResult = {
> ResultBase,
var medals:Array<Dynamic>;
}
typedef MedalUnlockResult = {
> ResultBase,
var medal_score:String;
var medal:Dynamic;
}
typedef ScoreBoardResult = {
> ResultBase,
var scoreboards:Array<Dynamic>;
}
typedef ScoreResult = {
> ResultBase,
var scores:Array<Score>;
var scoreboard:Dynamic;
}
typedef PostScoreResult = {
> ResultBase,
var tag:String;
var scoreboard:Dynamic;
var score:Score;
}

View File

@ -0,0 +1,23 @@
package io.newgrounds.swf;
import openfl.display.MovieClip;
class LoadingBar extends MovieClip {
public var bar(default, null):MovieClip;
public function new() {
super();
setProgress(0.0);
}
/**
*
* @param value The ratio of bytes loaded to bytes total
*/
public function setProgress(value:Float):Void {
bar.gotoAndStop(1 + Std.int(value * (bar.totalFrames - 1)));
}
}

View File

@ -0,0 +1,151 @@
package io.newgrounds.swf;
import io.newgrounds.swf.common.BaseAsset;
import io.newgrounds.objects.Medal;
import openfl.text.TextFieldAutoSize;
import openfl.text.TextField;
import openfl.display.DisplayObject;
import openfl.display.Loader;
import openfl.display.MovieClip;
import openfl.net.URLRequest;
import openfl.events.Event;
class MedalPopup extends BaseAsset {
static inline var FRAME_HIDDEN:String = "hidden";
static inline var FRAME_MEDAL_UNLOCKED:String = "medalUnlocked";
static inline var FRAME_INTRO_COMPLETE:String = "introComplete";
static inline var FRAME_UNLOCK_COMPLETE:String = "unlockComplete";
static inline var MIN_TEXT_SIZE:Int = 12;
public var medalIcon(default, null):MovieClip;
public var medalName(default, null):MovieClip;
public var medalPoints(default, null):MovieClip;
public var alwaysOnTop:Bool;
#if !ng_lite
public var requiresSession:Bool;
#end
var _animQueue = new Array<Void->Void>();
var _scrollSpeed:Float;
public function new() {
super();
mouseEnabled = false;
mouseChildren = false;
hide();
addFrameScript(totalFrames - 1, onUnlockAnimComplete);
}
function hide():Void {
visible = false;
gotoAndStop(FRAME_HIDDEN);
}
#if !ng_lite
override function onReady():Void {
super.onReady();
if (NG.core.medals != null)
onMedalsLoaded();
else
NG.core.onLogin.addOnce(NG.core.requestMedals.bind(onMedalsLoaded));
}
function onMedalsLoaded():Void {
for (medal in NG.core.medals)
medal.onUnlock.add(onMedalOnlock.bind(medal));
}
function onMedalOnlock(medal:Medal):Void {
if (requiresSession && !NG.core.loggedIn)
return;
var loader = new Loader();
loader.load(new URLRequest(medal.icon));
playAnim(loader, medal.name, medal.value);
}
#end
public function playAnim(icon:DisplayObject, name:String, value:Int):Void {
if (currentLabel == FRAME_HIDDEN)
playNextAnim(icon, name, value);
else
_animQueue.push(playNextAnim.bind(icon, name, value));
}
function playNextAnim(icon:DisplayObject, name:String, value:Int):Void {
visible = true;
gotoAndPlay(FRAME_MEDAL_UNLOCKED);
if (alwaysOnTop && parent != null) {
parent.setChildIndex(this, parent.numChildren - 1);
}
while(medalIcon.numChildren > 0)
medalIcon.removeChildAt(0);
cast(medalPoints.getChildByName("field"), TextField).text = Std.string(value);
var field:TextField = cast medalName.getChildByName("field");
field.autoSize = TextFieldAutoSize.LEFT;
field.x = 0;
field.text = "";
var oldWidth = medalName.width;
field.text = name;
_scrollSpeed = 0;
if (field.width > oldWidth + 4) {
field.x = oldWidth + 4;
initScroll(field);
}
medalIcon.addChild(icon);
}
function initScroll(field:TextField):Void {
//TODO: Find out why scrollrect didn't work
var animDuration = 0;
for (frame in currentLabels){
if (frame.name == FRAME_INTRO_COMPLETE )
animDuration -= frame.frame;
else if (frame.name == FRAME_UNLOCK_COMPLETE)
animDuration += frame.frame;
}
_scrollSpeed = (field.width + field.x + 4) / animDuration;
field.addEventListener(Event.ENTER_FRAME, updateScroll);
}
function updateScroll(e:Event):Void{
if (currentLabel == FRAME_INTRO_COMPLETE)
cast (e.currentTarget, TextField).x -= _scrollSpeed;
}
function onUnlockAnimComplete():Void {
cast (medalName.getChildByName("field"), TextField).removeEventListener(Event.ENTER_FRAME, updateScroll);
if (_animQueue.length == 0)
hide();
else
(_animQueue.shift())();
}
}

View File

@ -0,0 +1,250 @@
package io.newgrounds.swf;
import openfl.events.Event;
import io.newgrounds.swf.common.DropDown;
import io.newgrounds.objects.Score;
import io.newgrounds.objects.events.Result.ScoreBoardResult;
import io.newgrounds.objects.events.Result.ScoreResult;
import io.newgrounds.objects.events.Response;
import io.newgrounds.swf.common.BaseAsset;
import io.newgrounds.swf.common.Button;
import io.newgrounds.components.ScoreBoardComponent.Period;
import openfl.display.MovieClip;
import openfl.text.TextField;
class ScoreBrowser extends BaseAsset {
public var prevButton (default, null):MovieClip;
public var nextButton (default, null):MovieClip;
public var reloadButton (default, null):MovieClip;
public var listBox (default, null):MovieClip;
public var loadingIcon (default, null):MovieClip;
public var errorIcon (default, null):MovieClip;
public var scoreContainer(default, null):MovieClip;
public var titleField (default, null):TextField;
public var pageField (default, null):TextField;
public var period(get, set):Period;
function get_period():Period { return _periodDropDown.value; }
function set_period(value:Period):Period { return _periodDropDown.value = value; }
public var title(get, set):String;
function get_title():String { return titleField.text; }
function set_title(value:String):String { return titleField.text = value; }
public var tag(default, set):String;
function set_tag(value:String):String {
if (this.tag != value) {
this.tag = value;
delayReload();
}
return value;
}
public var social(default, set):Bool;
function set_social(value:Bool):Bool {
if (this.social != value) {
this.social = value;
delayReload();
}
return value;
}
public var boardId(default, set):Int;
function set_boardId(value:Int):Int {
_boardIDSet = true;
if (this.boardId != value) {
this.boardId = value;
delayReload();
}
return value;
}
public var page(default, set):Int;
function set_page(value:Int):Int {
if (this.page != value) {
this.page = value;
delayReload();
}
return value;
}
var _scores:Array<MovieClip>;
var _limit:Int = 0;
var _periodDropDown:DropDown;
var _boardIDSet:Bool;
public function new() { super(); }
override function setDefaults():Void {
super.setDefaults();
boardId = -1;
_boardIDSet = false;
scoreContainer.visible = false;
loadingIcon.visible = false;
reloadButton.visible = false;
errorIcon.visible = false;
errorIcon.addFrameScript(errorIcon.totalFrames - 1, errorIcon.stop);
//TODO: prevent memory leaks?
new Button(prevButton, onPrevClick);
new Button(nextButton, onNextClick);
new Button(reloadButton, reload);
_periodDropDown = new DropDown(listBox, delayReload);
_periodDropDown.addItem("Current day" , Period.DAY );
_periodDropDown.addItem("Current week" , Period.WEEK );
_periodDropDown.addItem("Current month", Period.MONTH);
_periodDropDown.addItem("Current year" , Period.YEAR );
_periodDropDown.addItem("All time" , Period.ALL );
_periodDropDown.value = Period.ALL;
_scores = new Array<MovieClip>();
while(true) {
var score:MovieClip = cast scoreContainer.getChildByName('score${_scores.length}');
if (score == null)
break;
new Button(score);
_scores.push(score);
}
_limit = _scores.length;
}
override function onReady():Void {
super.onReady();
if (boardId == -1 && !_boardIDSet) {
#if ng_lite
NG.core.calls.scoreBoard.getBoards()
.addDataHandler(onBoardsRecieved)
.queue();
#else
if (NG.core.scoreBoards != null)
onBoardsLoaded();
else
NG.core.requestScoreBoards(onBoardsLoaded);
#end
}
reload();
}
#if ng_lite
function onBoardsRecieved(response:Response<ScoreBoardResult>):Void {
if (response.success && response.result.success) {
for (board in response.result.data.scoreboards) {
NG.core.log('No boardId specified defaulting to ${board.name}');
boardId = board.id;
return;
}
}
}
#else
function onBoardsLoaded():Void {
for (board in NG.core.scoreBoards) {
NG.core.log('No boardId specified defaulting to ${board.name}');
boardId = board.id;
return;
}
}
#end
/** Used internally to avoid multiple server requests from various property changes in a small time-frame. **/
function delayReload():Void {
addEventListener(Event.EXIT_FRAME, onDelayComplete);
}
function onDelayComplete(e:Event):Void { reload(); }
public function reload():Void {
removeEventListener(Event.EXIT_FRAME, onDelayComplete);
errorIcon.visible = false;
scoreContainer.visible = false;
pageField.text = 'page ${page + 1}';
if (_coreReady && boardId != -1 && _limit > 0 && period != null) {
loadingIcon.visible = true;
NG.core.calls.scoreBoard.getScores(boardId, _limit, _limit * page, period, social, tag)
.addDataHandler(onScoresReceive)
.send();
}
}
function onScoresReceive(response:Response<ScoreResult>):Void {
loadingIcon.visible = false;
if (response.success && response.result.success) {
scoreContainer.visible = true;
var i = _limit;
while(i > 0) {
i--;
if (i < response.result.data.scores.length)
drawScore(i, response.result.data.scores[i], _scores[i]);
else
drawScore(i, null, _scores[i]);
}
} else {
errorIcon.visible = true;
errorIcon.gotoAndPlay(1);
reloadButton.visible = true;
}
}
inline function drawScore(rank:Int, score:Score, asset:MovieClip):Void {
if (score == null)
asset.visible = false;
else {
asset.visible = true;
cast (asset.getChildByName("nameField" ), TextField).text = score.user.name;
cast (asset.getChildByName("scoreField"), TextField).text = score.formatted_value;
cast (asset.getChildByName("rankField" ), TextField).text = Std.string(rank + 1);
}
}
function onPrevClick():Void {
if (page > 0)
page--;
}
function onNextClick():Void {
page++;
}
}

View File

@ -0,0 +1,35 @@
package io.newgrounds.swf.common;
import openfl.events.Event;
import openfl.display.MovieClip;
class BaseAsset extends MovieClip {
var _coreReady:Bool = false;
public function new() {
super();
setDefaults();
if (stage != null)
onAdded(null);
else
addEventListener(Event.ADDED_TO_STAGE, onAdded);
}
function setDefaults():Void { }
function onAdded(e:Event):Void {
if (NG.core != null)
onReady();
else
NG.onCoreReady.add(onReady);
}
function onReady():Void {
_coreReady = true;
}
}

View File

@ -0,0 +1,151 @@
package io.newgrounds.swf.common;
import openfl.display.Stage;
import openfl.events.Event;
import openfl.events.MouseEvent;
import openfl.display.MovieClip;
class Button {
var _enabled:Bool;
public var enabled(get, set):Bool;
function get_enabled():Bool { return _enabled; }
function set_enabled(value:Bool):Bool {
if (value != _enabled) {
_enabled = value;
updateEnabled();
}
return value;
}
public var onClick:Void->Void;
public var onOver:Void->Void;
public var onOut:Void->Void;
var _target:MovieClip;
var _down:Bool;
var _over:Bool;
var _foundLabels:Array<String>;
public function new(target:MovieClip, onClick:Void->Void = null, onOver:Void->Void = null, onOut:Void->Void = null) {
_target = target;
this.onClick = onClick;
this.onOver = onOver;
this.onOut = onOut;
_foundLabels = new Array<String>();
for (label in _target.currentLabels)
_foundLabels.push(label.name);
_target.stop();
_target.addEventListener(Event.ADDED_TO_STAGE, onAdded);
if (target.stage != null)
onAdded(null);
enabled = true;
}
function onAdded(e:Event):Void {
var stage = _target.stage;
stage.addEventListener(MouseEvent.MOUSE_UP, mouseHandler);
_target.addEventListener(MouseEvent.MOUSE_OVER, mouseHandler);
_target.addEventListener(MouseEvent.MOUSE_OUT, mouseHandler);
_target.addEventListener(MouseEvent.MOUSE_DOWN, mouseHandler);
_target.addEventListener(MouseEvent.CLICK, mouseHandler);
function selfRemoveEvent(e:Event):Void {
_target.removeEventListener(Event.REMOVED_FROM_STAGE, selfRemoveEvent);
onRemove(e, stage);
}
_target.addEventListener(Event.REMOVED_FROM_STAGE, selfRemoveEvent);
}
function onRemove(e:Event, stage:Stage):Void {
stage.removeEventListener(MouseEvent.MOUSE_UP, mouseHandler);
_target.removeEventListener(MouseEvent.MOUSE_OVER, mouseHandler);
_target.removeEventListener(MouseEvent.MOUSE_OUT, mouseHandler);
_target.removeEventListener(MouseEvent.MOUSE_DOWN, mouseHandler);
_target.removeEventListener(MouseEvent.CLICK, mouseHandler);
}
function mouseHandler(event:MouseEvent):Void {
switch(event.type) {
case MouseEvent.MOUSE_OVER:
_over = true;
if (onOver != null)
onOver();
case MouseEvent.MOUSE_OUT:
_over = false;
if (onOut != null)
onOut();
case MouseEvent.MOUSE_DOWN:
_down = true;
case MouseEvent.MOUSE_UP:
_down = false;
case MouseEvent.CLICK:
if (enabled && onClick != null)
onClick();
}
updateState();
}
function updateEnabled():Void {
updateState();
_target.useHandCursor = enabled;
_target.buttonMode = enabled;
}
function updateState():Void {
var state = determineState();
if (_target.currentLabel != state && _foundLabels.indexOf(state) != -1)
_target.gotoAndStop(state);
}
function determineState():String {
if (enabled) {
if (_over)
return _down ? "down" : "over";
return "up";
}
return "disabled";
}
public function destroy():Void {
_target.removeEventListener(Event.ADDED_TO_STAGE, onAdded);
_target = null;
onClick = null;
onOver = null;
onOut = null;
_foundLabels = null;
}
}

View File

@ -0,0 +1,88 @@
package io.newgrounds.swf.common;
import haxe.ds.StringMap;
import openfl.display.MovieClip;
import openfl.display.Sprite;
import openfl.text.TextField;
class DropDown {
public var value(default, set):String;
function set_value(v:String):String {
if (this.value == v)
return v;
this.value = v;
_selectedLabel.text = _values.get(v);
if (_onChange != null)
_onChange();
return v;
}
var _choiceContainer:Sprite;
var _selectedLabel:TextField;
var _onChange:Void->Void;
var _values:StringMap<String>;
var _unusedChoices:Array<MovieClip>;
public function new(target:MovieClip, label:String = "", onChange:Void->Void = null) {
_onChange = onChange;
_selectedLabel = cast cast(target.getChildByName("currentItem"), MovieClip).getChildByName("label");
_selectedLabel.text = label;
_values = new StringMap<String>();
new Button(cast target.getChildByName("button"), onClickExpand);
new Button(cast target.getChildByName("currentItem"), onClickExpand);
_choiceContainer = new Sprite();
_choiceContainer.visible = false;
target.addChild(_choiceContainer);
_unusedChoices = new Array<MovieClip>();
while(true) {
var item:MovieClip = cast target.getChildByName('item${_unusedChoices.length}');
if (item == null)
break;
target.removeChild(item);
_unusedChoices.push(item);
}
}
public function addItem(name:String, value:String):Void {
_values.set(value, name);
if (_unusedChoices.length == 0) {
NG.core.logError('cannot create another dropBox item max=${_choiceContainer.numChildren}');
return;
}
var button = _unusedChoices.shift();
cast(button.getChildByName("label"), TextField).text = name;
_choiceContainer.addChild(button);
new Button(button, onChoiceClick.bind(value));
}
function onClickExpand():Void {
_choiceContainer.visible = !_choiceContainer.visible;
}
function onChoiceClick(name:String):Void {
value = name;
_choiceContainer.visible = false;
}
}

View File

@ -0,0 +1,203 @@
package io.newgrounds.utils;
import io.newgrounds.NGLite;
import haxe.Http;
import haxe.Timer;
#if neko
import neko.vm.Thread;
#elseif java
import java.vm.Thread;
#elseif cpp
import cpp.vm.Thread;
#end
/**
* Uses Threading to turn hxcpp's synchronous http requests into asynchronous processes
*
* @author GeoKureli
*/
class AsyncHttp {
inline static var PATH:String = "https://newgrounds.io/gateway_v3.php";
static public function send
( core:NGLite
, data:String
, onData:String->Void
, onError:String->Void
, onStatus:Int->Void
) {
core.logVerbose('sending: $data');
#if (neko || java || cpp)
sendAsync(core, data, onData, onError, onStatus);
#else
sendSync(core, data, onData, onError, onStatus);
#end
}
static function sendSync
( core:NGLite
, data:String
, onData:String->Void
, onError:String->Void
, onStatus:Int->Void
):Void {
var http = new Http(PATH);
http.setParameter("input", data);
http.onData = onData;
http.onError = onError;
http.onStatus = onStatus;
// #if js http.async = async; #end
http.request(true);
}
#if (neko || java || cpp)
static var _deadPool:Array<AsyncHttp> = [];
static var _livePool:Array<AsyncHttp> = [];
static var _map:Map<Int, AsyncHttp> = new Map();
static var _timer:Timer;
static var _count:Int = 0;
var _core:NGLite;
var _key:Int;
var _onData:String->Void;
var _onError:String->Void;
var _onStatus:Int->Void;
var _worker:Thread;
public function new (core:NGLite) {
_core = core;
_worker = Thread.create(sendThreaded);
_key = _count++;
_map[_key] = this;
_core.logVerbose('async http created: $_key');
}
function start(data:String, onData:String->Void, onError:String->Void, onStatus:Int->Void) {
_core.logVerbose('async http started: $_key');
if (_livePool.length == 0)
startTimer();
_deadPool.remove(this);
_livePool.push(this);
_onData = onData;
_onError = onError;
_onStatus = onStatus;
_worker.sendMessage({ source:Thread.current(), args:data, key:_key, core:_core });
}
function handleMessage(data:ReplyData):Void {
_core.logVerbose('handling message: $_key');
if (data.status != null) {
_core.logVerbose('\t- status: ${data.status}');
_onStatus(cast data.status);
return;
}
var tempFunc:Void->Void;
if (data.data != null) {
_core.logVerbose('\t- data');
tempFunc = _onData.bind(data.data);
} else {
_core.logVerbose('\t- error');
tempFunc = _onError.bind(data.error);
}
cleanUp();
// Delay the call until destroy so that we're more likely to use a single
// thread on daisy-chained calls
tempFunc();
}
inline function cleanUp():Void {
_onData = null;
_onError = null;
_deadPool.push(this);
_livePool.remove(this);
if (_livePool.length == 0)
stopTimer();
}
static function sendAsync
( core:NGLite
, data:String
, onData:String->Void
, onError:String->Void
, onStatus:Int->Void
):Void {
var http:AsyncHttp;
if (_deadPool.length == 0)
http = new AsyncHttp(core);
else
http = _deadPool[0];
http.start(data, onData, onError, onStatus);
}
static function startTimer():Void {
if (_timer != null)
return;
_timer = new Timer(1000 / 60.0);
_timer.run = update;
}
static function stopTimer():Void {
_timer.stop();
_timer = null;
}
static public function update():Void {
var message:ReplyData = cast Thread.readMessage(false);
if (message != null)
_map[message.key].handleMessage(message);
}
static function sendThreaded():Void {
while(true) {
var data:LoaderData = cast Thread.readMessage(true);
data.core.logVerbose('start message received: ${data.key}');
sendSync
( data.core
, data.args
, function(reply ) { data.source.sendMessage({ key:data.key, data :reply }); }
, function(error ) { data.source.sendMessage({ key:data.key, error :error }); }
, function(status) { data.source.sendMessage({ key:data.key, status:status }); }
);
}
}
#end
}
#if (neko || java || cpp)
typedef LoaderData = { source:Thread, key:Int, args:String, core:NGLite };
typedef ReplyData = { key:Int, ?data:String, ?error:String, ?status:Null<Int> };
#end

View File

@ -0,0 +1,118 @@
package io.newgrounds.utils;
/**
* Basically shitty signals, but I didn't want to have external references.
**/
class Dispatcher {
var _list:Array<Void->Void>;
var _once:Array<Void->Void>;
public function new() {
_list = new Array<Void->Void>();
_once = new Array<Void->Void>();
}
public function add(handler:Void->Void, once:Bool = false):Bool {
if (_list.indexOf(handler) != -1) {
// ---- REMOVE ONCE
if (!once && _once.indexOf(handler) != -1)
_once.remove(handler);
return false;
}
_list.unshift(handler);
if (once)
_once.unshift(handler);
return true;
}
inline public function addOnce(handler:Void->Void):Bool {
return add(handler, true);
}
public function remove(handler:Void->Void):Bool {
_once.remove(handler);
return _list.remove(handler);
}
public function dispatch():Void {
var i = _list.length - 1;
while(i >= 0) {
var handler = _list[i];
if (_once.remove(handler))
_list.remove(handler);
handler();
i--;
}
}
}
class TypedDispatcher<T> {
var _list:Array<T->Void>;
var _once:Array<T->Void>;
public function new() {
_list = new Array<T->Void>();
_once = new Array<T->Void>();
}
public function add(handler:T->Void, once:Bool = false):Bool {
if (_list.indexOf(handler) != -1) {
// ---- REMOVE ONCE
if (!once && _once.indexOf(handler) != -1)
_once.remove(handler);
return false;
}
_list.unshift(handler);
if (once)
_once.unshift(handler);
return true;
}
inline public function addOnce(handler:T->Void):Bool {
return add(handler, true);
}
public function remove(handler:T->Void):Bool {
_once.remove(handler);
return _list.remove(handler);
}
public function dispatch(arg:T):Void {
var i = _list.length - 1;
while(i >= 0) {
var handler = _list[i];
if (_once.remove(handler))
_list.remove(handler);
handler(arg);
i--;
}
}
}